Production-grade, fully database-backed door system for FiveM.
Built on ox_core Β· ox_inventory Β· ox_lib Β· ox_target Β· Nostr logging Β· Triple admin verification
Built by Red Dragon Elite | SerpentsByte Β· v4.0.0
- Why RDE Doors?
- Features
- Dependencies
- Installation
- Configuration
- Exports & Developer API
- Admin Commands
- Nostr Logging
- Database Schema
- Troubleshooting
- License
Most door scripts are static config files. You restart the server, you lose runtime changes. RDE Doors is different:
| Feature | Config-based scripts | RDE Doors v3 | RDE Doors v4 |
|---|---|---|---|
| Runtime door creation | β | β | β |
| Persistent ownership | β | β Database-backed | β |
| Per-player access lists | β | β | β |
| Item-based access | β | β ox_inventory | β |
| Item access with metadata | β | β | β v4 |
| Group access with grade requirement | β | β | β v4 |
| Door groups | β | β | β |
| Decentralized logging | β | β Nostr | β |
| Admin triple verification | β | β ACE + ox_core + groups | β |
| Autolock timers | β | β | β |
| Bell & knock system | β | β | β |
| Price / buyable doors | β | β | β |
| Double doors | β | β | β |
| Sliding / automatic doors | β | β | β v4 |
| Garage doors | β | β | β v4 |
| Gate type | β | β | β v4 |
| Lockpick system (skill check) | β | β | β v4 |
| Passcode unlock | β | β | β v4 |
| Per-door custom sounds | β | β | β v4 |
| Hide UI per door | β | β | β v4 |
| Hold open when unlocked | β | β | β v4 |
| Per-door interact distance | β | β | β v4 |
| Auto-migration (zero downtime) | β | β | β v4 |
- Single β Standard single door
- Double β Two entities that open and lock together, perfectly synchronized
- Sliding β Automatic sliding door (zero door-rate, instant open)
- Garage β Large garage door, automatic by default
- Gate β Large entrance gate, automatic by default
- Full CRUD β Create, update, delete doors at runtime via ox_target or admin commands
- Database persistence β All doors survive server restarts (oxmysql)
- StateBag sync β Real-time door state broadcast to all players
- Coordinate deduplication β Prevents overlapping doors at the same position
- Server-side validation β All input validated on the server; client is never trusted
- Admin β Triple-verified (ACE permission + ox_core groups + Steam ID fallback)
- Owner β The character who created or purchased the door
- Access list β Per-character allowlist (granted by owner or admin)
- Legacy auth array β Group name only, grade 0 (backward compatible)
- groups_data β Group name + minimum grade requirement (v4)
- items_data β Item name + optional metadata + consume-on-use flag (v4)
- Legacy items array β Simple item check, no metadata (backward compatible)
- Passcode β Anyone with the correct code can toggle (v4)
- Configurable lockpick items (
lockpick,advancedlockpick, or custom) - Per-door difficulty:
easy,medium,hardβ or fully custom as JSON array - ox_lib skill check mini-game with configurable keys
- Item break chance on both success and failure (configurable %)
- Server-side verification β client skillcheck result is still validated server-side
- Cooldown between pick attempts (anti-spam)
- Set a numeric or text passcode per door via admin menu
- Passcode is never sent to the client β only a
has_passcodeboolean is synced - Players without group/item/owner access get prompted for the code when interacting
- Configurable per-door autolock timer (seconds)
- Cancel-safe: manually re-locking the door cancels the pending autolock
- Nostr-logged when autolock fires
- Ring doorbell β owner gets ox_lib notification with caller name and door name
- Knock β same notification system with knock animation
- Create named groups containing multiple doors
- Lock/unlock an entire group at once
- Full group CRUD with persistent storage
- Group membership shown in 3D text overlay
- Per-door custom lock/unlock sounds (GTA audio set + sound name)
- Falls back to configured defaults when no custom sound is set
- Default: Vinewood Casino door sounds
- Per-door flag: keep door physically open when unlocked using
DoorSystemSetHoldOpen - Automatically closes (locks) when door is toggled back to locked state
- Per-door flag to suppress 3D text and ox_target indicators
- Useful for ambient world doors that should lock silently
All door interactions are handled exclusively through ox_target β no key prompts, no E-button, no NUI input required. Walking up to a door shows the ox_target crosshair with contextual options based on your access level:
| Player type | Options shown |
|---|---|
| Any player | Lock/Unlock (if access), Ring Bell, Knock, Buy (if for sale) |
| Player with lockpick item | Pick Lock (if door is lockpick-enabled) |
| Owner | + Manage (set price, access list, rename, teleport) |
| Admin | + Admin Menu (full edit, advanced settings, group management, delete) |
/createdoorβ Select a door entity in the world and register it (single or double)/doormanagerβ Open full door manager menu (lists all doors with lock state)/doorslistβ Print all doors to server console with validity status and v4 flags/resyncdoorsβ Force-reload all doors from DB and resync to every player/cleandoorsβ Scan and remove corrupted/invalid door entries from the DB/doorinfoβ Get detailed info on the nearest door (within 10m) in server console/doordebug(debug mode) β Print client-side door stats to F8 console
Every significant door event is broadcast to the Nostr network via rde_nostr_log β decentralized, permanent, uncensorable. If rde_nostr_log is not running, logging is silently skipped β no errors, no crashes.
| Resource | Required | Notes |
|---|---|---|
| oxmysql | β | Database layer |
| ox_core | β | Player/character framework (v3+) |
| ox_lib | β | Callbacks, commands, skill checks, notifications |
| ox_target | β | Door interaction (all interactions go through ox_target) |
| ox_inventory | β | Item-based access, lockpick items, money transactions |
| rde_nostr_log | Decentralized logging β highly recommended |
cd resources
git clone https://github.com/RedDragonElite/rde_doors.gitensure oxmysql
ensure ox_core
ensure ox_lib
ensure ox_target
ensure ox_inventory
ensure rde_nostr_log # optional but recommended
ensure rde_doors
Order matters.
rde_doorsmust start after all its dependencies.
start rde_doors
The database tables are created and migrated automatically on first start. No manual SQL import needed.
# server.cfg
add_ace group.admin rde.doors.admin allow
add_principal identifier.steam:YOURSTEAMHEX group.admin
Check your server console β you should see:
[RDE Doors v4.0.0] β
Ready β X doors | X lockpickable | X automatic
[RDE | Doors | Server v4.0.0] π Ready
Edit shared/config.lua:
Config.DefaultLanguage = 'en' -- 'en' or 'de'
Config.Debug = true -- verbose server console logging
Config.Defaults = {
locked = true,
autolock = 0, -- 0 = disabled, seconds otherwise
maxDistance = 2.5, -- ox_target interaction radius (meters)
heading = 0,
price = 0, -- 0 = not for sale
type = 'single',
doorRateSwing = 10.0, -- swing door speed (higher = slower)
doorRateAuto = 0.0, -- automatic/sliding door speed (0 = instant)
}
Config.AdminSystem = {
acePermission = 'rde.doors.admin',
oxGroups = {
['admin'] = 0,
['superadmin'] = 0,
['management'] = 0,
},
}
Config.Lockpick = {
items = { 'lockpick', 'advancedlockpick' },
defaultDifficulty = { 'easy', 'easy', 'medium' },
breakChanceOnFail = 0.20, -- 20% break on failed pick
breakChanceOnSuccess = 0.05, -- 5% break on successful pick
animDict = 'mp_common_heist',
animName = 'pick_door',
canPickUnlocked = false,
cooldownMs = 1500,
}
Config.Sounds = {
lockDefault = { name = 'door_lock', set = 'dlc_vinewood_casino_door_sounds' },
unlockDefault = { name = 'door_unlock', set = 'dlc_vinewood_casino_door_sounds' },
}Garage, sliding, and gate door types default to auto = true (instant open/close). You can override this per door in the admin menu.
Config.DoorTypes = {
['single'] = { name = 'Single Door', autoDefault = false },
['double'] = { name = 'Double Door', autoDefault = false },
['garage'] = { name = 'Garage Door', autoDefault = true },
['sliding'] = { name = 'Sliding Door', autoDefault = true },
['gate'] = { name = 'Gate', autoDefault = true },
}TriggerEvent('rde_doors:createDoor', {
name = 'Police HQ Front Door',
coords = { x = 462.12, y = -993.47, z = 27.79 },
model = 'prop_door_01',
type = 'single', -- single | double | sliding | garage | gate
locked = true,
auth = { 'police' }, -- legacy: group name only
groups_data = { police = 2 }, -- v4: group + minimum grade
items = { 'keycard_pd' }, -- legacy: item name only
items_data = { { name = 'keycard_pd', metadata = 'hq', remove = false } }, -- v4
autolock = 30,
heading = 0.0,
maxDistance = 2.5,
price = 0,
auto = false,
door_rate = nil, -- nil = derived from auto flag
lockpick = false,
passcode = nil,
hide_ui = false,
hold_open = false,
})TriggerServerEvent('rde_doors:toggleLock', doorId)
-- With passcode:
TriggerServerEvent('rde_doors:toggleLock', doorId, '1337')TriggerServerEvent('rde_doors:updateDoor', doorId, {
name = 'New Name',
price = 5000,
auth = { 'police', 'ambulance' },
-- v4 fields (admin only):
autolock = 60,
lockpick = true,
passcode = '9999',
auto = true,
door_rate = 0.0,
hold_open = true,
hide_ui = false,
maxDistance = 3.0,
lock_sound = 'door_lock',
unlock_sound = 'door_unlock',
groups_data = { police = 2, sheriff = 3 },
items_data = { { name = 'badge', metadata = 'police_hq', remove = false } },
})TriggerServerEvent('rde_doors:deleteDoor', doorId)-- Grant access to a player (server ID)
TriggerServerEvent('rde_doors:manageAccess', doorId, targetServerId, true)
-- Revoke access by charId
TriggerServerEvent('rde_doors:manageAccess', doorId, charId, false)-- Add group with min grade
TriggerServerEvent('rde_doors:manageGroup', doorId, 'police', 2, true)
-- Remove group
TriggerServerEvent('rde_doors:manageGroup', doorId, 'police', 0, false)-- Add item with metadata
TriggerServerEvent('rde_doors:manageItemData', doorId, 'badge', 'police_hq', false, true)
-- { itemName, metadata, removeOnUse, grantAccess }
-- Remove item
TriggerServerEvent('rde_doors:manageItemData', doorId, 'badge', 'police_hq', false, false)TriggerServerEvent('rde_doors:createGroup', 'Police HQ')
TriggerServerEvent('rde_doors:addToGroup', doorId, groupId)
TriggerServerEvent('rde_doors:removeFromGroup', doorId, groupId)
TriggerServerEvent('rde_doors:renameGroup', groupId, 'New Group Name')
TriggerServerEvent('rde_doors:deleteGroup', groupId)-- Get a door object by ID
local door = exports.rde_doors:getDoor(doorId)
-- Get a door by name
local door = exports.rde_doors:getDoorFromName('Police HQ Front Door')
-- Get all doors (safe client representation, passcode excluded)
local doors = exports.rde_doors:getAllDoors()
-- Set door state programmatically (bypasses access checks)
exports.rde_doors:setDoorState(doorId, true) -- true = locked
exports.rde_doors:setDoorState(doorId, false, 'my_script') -- with reason
-- Edit door data programmatically (bypasses access checks)
exports.rde_doors:editDoor(doorId, { name = 'New Name', lockpick = true })-- Listen for any lock state change (fired from server)
AddEventHandler('rde_doors:stateChanged', function(source, doorId, locked, reason)
-- source: player source or nil if autolock
-- reason: 'owner', 'access_list', 'group_grade', 'item_meta:badge', 'passcode', 'lockpick:lockpick', 'autolock', 'export'
print('Door', doorId, 'is now', locked and 'locked' or 'unlocked', 'by', reason)
end)-- Check if local player is admin
lib.callback('rde_doors:checkAdmin', false, function(isAdmin)
print('Is admin:', isAdmin)
end)
-- Check if local player has access to a door
lib.callback('rde_doors:checkAccess', false, function(hasAccess)
print('Has access:', hasAccess)
end, doorId)
-- Buy a door (deducts money item from ox_inventory)
lib.callback('rde_doors:buyDoor', false, function(success, message)
if success then print('Purchased!') end
end, doorId)
-- Verify passcode (returns bool, does NOT toggle door state)
lib.callback('rde_doors:verifyPasscode', false, function(correct)
print('Passcode correct:', correct)
end, doorId, '1337')-- Fired when all doors are synced to this client
AddEventHandler('rde_doors:syncDoors', function(doorArray, doorGroups)
end)
-- Fired when a single door state changes
AddEventHandler('rde_doors:doorUpdate', function(doorId, doorData)
end)
-- Fired when a door is deleted
AddEventHandler('rde_doors:doorDeleted', function(doorId)
end)
-- Fired when a door group changes
AddEventHandler('rde_doors:doorGroupUpdate', function(groupId, groupData)
end)
-- Fired when a group is deleted
AddEventHandler('rde_doors:doorGroupDeleted', function(groupId)
end)
-- Feedback after any action (success/fail notification)
AddEventHandler('rde_doors:actionFeedback', function(success, message, doorId, action)
end)
-- Lockpick result (success/fail + broke)
AddEventHandler('rde_doors:lockpickResult', function(success, doorId, broken)
end)-- Get closest door object
local door = exports.rde_doors:getClosestDoor()
-- Toggle closest door (if player has access)
exports.rde_doors:useClosestDoor()
-- Lockpick closest lockpickable door
exports.rde_doors:pickClosestDoor()All in-game commands require the rde.doors.admin ACE permission or an admin ox_core group.
| Command | Where | Description |
|---|---|---|
/createdoor |
In-game | Click a door entity in the world to register it (single or double) |
/doormanager |
In-game | Open full door manager UI (all doors listed with lock state) |
/doorslist |
Console / In-game | Print all doors to server console with validity and v4 flags |
/resyncdoors |
Console / In-game | Reload all doors from DB and push to every connected player |
/cleandoors |
Console / In-game | Remove corrupted/invalid door records from the database |
/doorinfo |
In-game | Print detailed info about the closest door (β€ 10m) to server console |
/doordebug |
In-game | F8 debug dump β loaded door count, double door count, active targets |
RDE Doors integrates natively with rde_nostr_log β the world's first decentralized FiveM logging system.
If rde_nostr_log is not running, logging is silently skipped β no errors, no crashes.
| Event | Message |
|---|---|
| Door locked/unlocked | π Door Locked/Unlocked | Name | By: Player | Auth: reason |
| Door created | β
Door created: Name (type) | By: Player |
| Door updated | π Door updated: Name | By: Player |
| Door deleted | ποΈ Door deleted: ID | By: Player |
| Door purchased | π³ Door purchased: Name | By: Player (CharID: X) |
| Lockpick success | π§ Lockpick: Name | By: Player | Item: lockpick |
| Lockpick broke | π₯ Lockpick broke: Player | Item: lockpick |
| Autolock fired | β±οΈ Autolock: Door Name |
| DB migration | π§ DB Migration: N column(s) added |
cd resources
git clone https://github.com/RedDragonElite/rde_nostr_log.git
cd rde_nostr_log
yarn installEnsure before rde_doors in server.cfg:
ensure rde_nostr_log
Tables are created and migrated automatically. The door_sql.sql file is included for reference only β you do not need to run it manually.
| Version | Columns Added |
|---|---|
| v2.x β v3.0.0 | double_door_data |
| v3.0.0 β v4.0.0 | passcode, auto, door_rate, lockpick, lockpick_difficulty, hide_ui, hold_open, lock_sound, unlock_sound, groups_data, items_data |
Zero data loss guaranteed. Downgrading from v4 to v3 is safe β v3 ignores the new columns.
| Column | Type | Description |
|---|---|---|
id |
VARCHAR(50) | Unique door ID (auto-generated) |
type |
VARCHAR(20) | single, double, sliding, garage, gate |
name |
VARCHAR(100) | Display name |
coords |
LONGTEXT | JSON {x, y, z} β midpoint for double doors |
model |
VARCHAR(100) | Model name string (empty for double doors) |
model_hash |
VARCHAR(50) | Integer hash from GetEntityModel() |
locked |
TINYINT(1) | 1 = locked |
auth |
LONGTEXT | JSON array of group names (legacy) |
autolock |
INT | Seconds until relock (0 = off) |
items |
LONGTEXT | JSON array of item names (legacy) |
heading |
FLOAT | Door heading |
maxDistance |
FLOAT | ox_target interaction radius |
owner_charid |
VARCHAR(50) | Owning character ID |
owner_name |
VARCHAR(100) | Owning character name |
price |
INT | Purchase price (0 = not for sale) |
access_list |
LONGTEXT | JSON array of charIds with access |
group_id |
VARCHAR(50) | Door group membership |
double_door_data |
LONGTEXT | JSON {door_a, door_b} with model + coords + heading |
passcode |
VARCHAR(100) | v4: unlock passcode (never sent to client) |
auto |
TINYINT(1) | v4: 1 = automatic/sliding mode |
door_rate |
FLOAT | v4: movement speed (NULL = derived from auto flag) |
lockpick |
TINYINT(1) | v4: 1 = lockpickable |
lockpick_difficulty |
LONGTEXT | v4: JSON difficulty array e.g. ["easy","medium","hard"] |
hide_ui |
TINYINT(1) | v4: 1 = suppress 3D text and target indicator |
hold_open |
TINYINT(1) | v4: 1 = keep door physically open when unlocked |
lock_sound |
VARCHAR(100) | v4: custom lock sound name |
unlock_sound |
VARCHAR(100) | v4: custom unlock sound name |
groups_data |
LONGTEXT | v4: JSON {groupName: minGrade} |
items_data |
LONGTEXT | v4: JSON [{name, metadata, remove}] |
created_at |
TIMESTAMP | Creation time |
updated_at |
TIMESTAMP | Last update |
| Column | Type | Description |
|---|---|---|
id |
VARCHAR(50) | Unique group ID |
name |
VARCHAR(100) | Group display name |
doors |
LONGTEXT | JSON array of door IDs |
created_at |
TIMESTAMP | Creation time |
updated_at |
TIMESTAMP | Last update |
The correct ox:playerLoaded server event signature for ox_core v3+ is:
-- CORRECT:
AddEventHandler('ox:playerLoaded', function(playerId, userId, charId)
-- playerId = server source ID (number)
-- userId = user ID (number)
-- charId = character ID (number)
end)
-- WRONG β causes the crash:
AddEventHandler('ox:playerLoaded', function(source, player)
player.charId -- β player is actually userId (a number), not a table
end)Already fixed in v4.0.0.
The correct export name is postLog:
-- CORRECT:
exports['rde_nostr_log']:postLog('Your message', {{'event', 'event_type'}})
-- WRONG:
exports['rde_nostr_log']:log(...)Run /resyncdoors in the server console. If doors still don't appear, verify that oxmysql is fully started before rde_doors in server.cfg:
ensure oxmysql # must come first
ensure ox_core
ensure ox_lib
ensure ox_target
ensure ox_inventory
ensure rde_doors
Ensure oxmysql starts before rde_doors. The resource waits for oxmysql to be in started state before initializing.
The model must exist client-side. Verify the model string or model_hash matches an actual loaded prop. Use /doorinfo while standing near the expected position to confirm the door was saved with correct coordinates.
Note on model hashes: RDE Doors stores the integer result of
GetEntityModel()directly. This is the only value GTA'sAddDoorToSystemaccepts reliably β many door entities have no registered string name, soGetHashKey("model_name")is not used as primary source.
Verify your ACE setup in server.cfg:
add_ace group.admin rde.doors.admin allow
add_principal identifier.steam:YOURSTEAMHEX group.admin
Or add your ox_core group to Config.AdminSystem.oxGroups in config.lua.
Player uses ox_target on door
β onSelect fires
β TriggerServerEvent('rde_doors:toggleLock', doorId)
Server: toggleLock
β HasAccess() β 8-layer check in order
β SetDoorState(doorId, newLocked, reason, source)
β doors[doorId].locked = newLocked
β SaveDoor() β async MySQL UPDATE
β BroadcastDoorUpdate(-1, doorId, door)
β TriggerClientEvent('rde_doors:doorUpdate', -1, ...)
β Client: SetDoorLockState() β updates GTA door system
β Client: refreshes ox_target options (lock/unlock label flips)
β Client: plays lock/unlock sound
β if autolock > 0 and unlocked: SetTimeout(autolock*1000, re-lock)
β TriggerEvent('rde_doors:stateChanged', source, doorId, locked, reason)
β NostrLog(...)
- Full double door support with correct heading and entity sync
- Sliding / automatic door type (configurable door rate)
- Garage and gate door types
- Lockpick system with ox_lib skill check
- Passcode unlock (server-authoritative, never sent to client)
- Per-door custom sounds
- Hold open flag
- Hide UI flag
- Per-door interact distance
- Group + grade access system
- Item + metadata access system
- Auto-migration from v3 to v4 (zero downtime)
- ox_doorlock-compatible client exports
- AETHER admin panel NUI (visual door manager overlay)
- Expanded multi-language support
###################################################################################
# #
# .:: RED DRAGON ELITE (RDE) - BLACK FLAG SOURCE LICENSE v6.66 ::. #
# #
# PROJECT: RDE_DOORS v4.0.0 (NEXT-LEVEL FIVEM DOOR SYSTEM) #
# ARCHITECT: .:: RDE β§ Shin [β³ αα
α±αα
αΎαα αααα
β½] ::. | https://rd-elite.com #
# ORIGIN: https://github.com/RedDragonElite #
# #
# WARNING: THIS CODE IS PROTECTED BY DIGITAL VOODOO AND PURE HATRED FOR LEAKERS #
# #
# [ THE RULES OF THE GAME ] #
# #
# 1. // THE "FUCK GREED" PROTOCOL (FREE USE) #
# You are free to use, edit, and abuse this code on your server. #
# Learn from it. Break it. Fix it. That is the hacker way. #
# Cost: 0.00β¬. If you paid for this, you got scammed by a rat. #
# #
# 2. // THE TEBEX KILL SWITCH (COMMERCIAL SUICIDE) #
# Listen closely, you parasites: #
# If I find this script on Tebex, Patreon, or in a paid "Premium Pack": #
# > I will DMCA your store into oblivion. #
# > I will publicly shame your community. #
# > I hope your server lag spikes to 9999ms every time you blink. #
# SELLING FREE WORK IS THEFT. AND I AM THE JUDGE. #
# #
# 3. // THE CREDIT OATH #
# Keep this header. If you remove my name, you admit you have no skill. #
# You can add "Edited by [YourName]", but never erase the original creator. #
# Don't be a skid. Respect the architecture. #
# #
# 4. // THE CURSE OF THE COPY-PASTE #
# This code uses advanced logic and cryptographic patterns. #
# If you just copy-paste without reading, it WILL break. #
# Don't come crying to my DMs. RTFM or learn to code. #
# #
# -------------------------------------------------------------------------- #
# "We build the future on the graves of paid resources." #
# "REJECT MODERN MEDIOCRITY. EMBRACE RDE SUPERIORITY." #
# -------------------------------------------------------------------------- #
###################################################################################
TL;DR:
- β Free forever β use it, edit it, learn from it
- β Keep the header β credit where it's due
- β Don't sell it β commercial use = instant DMCA
- β Don't be a skid β copy-paste without reading won't work anyway
| π GitHub | RedDragonElite |
| π Website | rd-elite.com |
| π΅ Nostr | npub1wr4e24zn6zzjqx8kvnelfvktf0pu6l2gx4gvw06zead2eqyn23sq9tsd94 |
| π€ RDE Nostr Log | rde_nostr_log |
When asking for help, always include:
- Full error from server console (F8 or txAdmin)
- Your
server.cfgresource order - ox_core / ox_lib versions
Please DON'T:
- β DM for basic setup questions β read the docs first
- β Open issues without error logs
- β Ask for paid support β this is free software
Please DO:
- β Star the repo if it helped you
- β Open issues with proper reproduction steps
- β Share your setup β community feedback makes this better
"We build the future on the graves of paid resources."
REJECT MODERN MEDIOCRITY. EMBRACE RDE SUPERIORITY.
π Made with π₯ by Red Dragon Elite