Custom bridges
bridge/ is outside escrow. Copy the closest file, adapt a handful of functions, and dd-cards will talk to a framework, inventory, target, menu or phone it does not ship support for.
dd-cards reaches the rest of your server only through bridge/. That whole directory is listed under escrow_ignore, so it stays readable and editable after purchase — which means adding support for a
resource that is not on the shipped list is a matter of copying one file.
Every category works the same way: a dispatcher (bridge/framework.lua and friends) resolves the
implementation name from config, requires bridge/<name>/..., and errors clearly if the file is
missing.
The pattern
- Copy the reference file named in the table below to the new path.
- Adapt each function to your resource.
- Point the matching config option at your new name.
-- config/shared.lua
Framework = 'my_framework', -- loads bridge/my_framework/server.luaThe dispatcher checks the file exists before requiring it, so a typo produces no framework bridge at bridge/my_framework/server.lua. Check Config.Framework. rather than a
stack trace.
Framework
Copy bridge/qbx_core/server.lua to bridge/<name>/server.lua, then set Config.Framework.
---@class FrameworkBridge
---@field getCitizenId fun(source: number): string?
---@field getMoney fun(source: number, account: string): number
---@field removeMoney fun(source: number, account: string, amount: number, reason: string): boolean
---@field addMoney fun(source: number, account: string, amount: number, reason: string): boolean
---@field resolveSourceByCitizenId fun(citizenid: string): number?account is 'cash' or 'bank', matching DefaultPaymentMethod. resolveSourceByCitizenId is what lets an order finishing find a player who has since reconnected on
a different server id — return nil when they are offline.
Inventory
Copy bridge/ox_inventory/server.lua to bridge/<name>/server.lua, then set Config.Inventory.
---@class InventorySlot
---@field name string
---@field metadata table? normalised: qb-inventory calls this `info`
---@class InventoryBridge
---@field canCarry fun(source: number, item: string, count: number): boolean
---@field addItem fun(source: number, item: string, count: number, metadata: table?): boolean
---@field removeItem fun(source: number, item: string, count: number, slot: number?): boolean
---@field getItemCount fun(source: number, item: string): number
---@field setMetadata fun(source: number, slot: number, metadata: table)
---@field getSlot fun(source: number, slot: number): InventorySlot?
---@field registerUseableItems fun(handlers: table<string, fun(source: number, slot: number)>)Interaction (targeting)
Copy bridge/ox_target/client.lua to bridge/<name>/client.lua, then set Config.Interaction.
---@class TargetBridge
---@field addFabric fun(entity: number, options: { name: string, icon: string, label: string, distance: number, onSelect: fun() }[])
---@field removeFabric fun(entity: number, names: string[])Only two functions, because dd-cards attaches a single option per fabric ped and removes it by name when the ped despawns.
Menu
Copy bridge/menu/ox_lib.lua to bridge/menu/<name>.lua, then set Config.Menu.
---@class MenuOption
---@field title string
---@field description string?
---@field icon string?
---@field disabled boolean?
---@field onSelect fun()?
---@class MenuProps
---@field id string
---@field title string
---@field back { menu: string, onSelect: fun() }? a way back to the parent menu
---@field onExit fun()?
---@field options MenuOption[]
---@class MenuBridge
---@field open fun(props: MenuProps)
---@field close fun()
---@field isOpen fun(id: string): boolean
---@field supportsLiveRefresh boolean whether reopening an already-open menu in place is safeSet supportsLiveRefresh = false if your menu resource cannot report whether the player has closed
it. dd-cards then stops refreshing the order list’s countdown in place, rather than risking
reopening a menu the player just dismissed — this is exactly why qb-menu’s countdown only advances
on reopen.
Notification
Copy bridge/notify/ox_lib.lua to bridge/notify/<name>.lua, then set Config.Notification.provider.
---@class NotifyTarget
---@field source number? nil when the player is offline
---@field citizenid string
---@class NotifyBridge
---@field send fun(target: NotifyTarget, opts: { title: string, description: string? })target.source is nil when the player is offline. A phone that addresses mail by citizenid can
still deliver in that case; one that addresses by live phone number cannot, and should return
without doing anything. See offline delivery.
Where each dispatcher lives
| Category | Dispatcher | Reference file to copy | Config option |
|---|---|---|---|
| Framework | bridge/framework.lua | bridge/qbx_core/server.lua | Config.Framework |
| Inventory | bridge/inventory.lua | bridge/ox_inventory/server.lua | Config.Inventory |
| Interaction | bridge/target.lua | bridge/ox_target/client.lua | Config.Interaction |
| Menu | bridge/menu.lua | bridge/menu/ox_lib.lua | Config.Menu |
| Notification | bridge/notify.lua | bridge/notify/ox_lib.lua | Config.Notification.provider |
Each dispatcher carries the same instruction as a comment at the top of the file, so you never have to come back here to remember which file to copy.