Skip to main content

BenzyBridge — Integrations

How to use the bridge from a resource, and how to add support for a system it doesn't know yet. Everything a resource asks the bridge for is provider-agnostic — wire it once and it keeps working when you swap the underlying system. The bridge is standalone-first: with no framework, no inventory and no money system running it still answers every call sensibly (see With and without a framework).

Startup order. In server.cfg, always ensure BenzyBridge before the resources that use it (and after your SQL connector, if any).

Contents


Adding the bridge to a resource

Add the bridge as your resource's first shared_script, and (optionally) declare the dependency:

-- your resource's fxmanifest.lua
shared_script '@BenzyBridge/init.lua' -- must be the FIRST shared_script
dependency 'BenzyBridge'

init.lua runs inside your resource's Lua state: it fetches the resolved settings, loads the active framework / inventory / target adapters in-process, and builds one global — Benzy. From then on you call Benzy.* directly. It's an ordinary Lua call, no cross-resource export hop, and nothing but Benzy is added to your globals.

-- Server
Benzy.Notify(source, 'success', 'Job', 'Paid $500.')
local ok = Benzy.AddMoney(source, 'cash', 500, 'Payout')
if Benzy.HasItem(source, 'lockpick') then --[[ ... ]] end

-- Client
Benzy.Show('E', 'Open the door')
local money = Benzy.GetTotal()

-- Read the resolved stack any time
print(Benzy.Framework, Benzy.Money, Benzy.Settings.storage)

Guard startup on the bridge if your resource requires it:

if GetResourceState('BenzyBridge') ~= 'started' then
-- warn + disable: this resource depends on the bridge
end

What the bridge auto-detects

With a concern left at 'auto' (the default for all of them), the bridge picks the first supported provider that is running, in this order — else the fallback in the last column. Force any concern by naming it in config.lua instead of 'auto'.

ConcernDetected in orderFallback
Frameworkes_extendedesx, qbx_coreqbox, qb-coreqbcorestandalone
Inventoryox_inventory, qb-inventory, qs-inventorynone (item ops are silent no-ops)
Moneya framework's own money if a framework is active, else BenzyMoneynone (money ops return false, 'UNSUPPORTED')
NotifyBenzyNotificationsnative (native toast / chat)
DrawText (prompts)BenzyDrawTextnative (native prompt)
Targetox_target, qb-targetnone (a script that wants targeting downgrades to draw-text)
Storageoxmysql, mysql-asyncfile (JSON files)

The stack is re-resolved whenever a resource starts or stops, so starting BenzyMoney or a targeting system mid-session is picked up. Check what resolved with the server console command debugbridge, or read it live with exports.BenzyBridge:GetSettings().


What you get with and without

Every concern degrades gracefully — a missing system is never an error, just a smaller feature set. This is what a consumer sees in each case:

Framework

  • With a framework (ESX / QBCore / QBox) — GetPlayer, GetJob, GetGang, HasJob, OnDuty return real character / job / gang data, and the core cash/bank money ops route to the framework's own wallet.
  • Without (standalone) — you still get a normalized player (source / id / license / name, an empty job/gang, and money read from the money provider). Job and gang come back empty rather than failing, so job-gated code just sees "no job".

Inventory

  • With an inventory (ox / qb / qs) — HasItem, GetItemCount, AddItem, RemoveItem, CanCarry operate on real items. The item definitions and images live in your inventory, not the bridge — a resource that grants items ships its own list and setup steps (e.g. the BenzyFishing Integrations documentation).
  • Without (none) — item ops are silent no-ops: HasInventory() returns false, AddItem/RemoveItem do nothing and don't warn. Branch on Benzy.HasInventory() and keep your own database tracking when it's false. This is normal standalone behavior, not a fault.

Money

  • With BenzyMoney — the full money surface works: cash/bank, society + business accounts, requests, loans, fines, and economy stats.
  • With a framework's money — only the core cash/bank subset works; every account / request / loan op returns false, 'UNSUPPORTED', so richer features fall away cleanly.
  • Without (Config.Money = 'none', or nothing detected) — every money op returns false, 'UNSUPPORTED'. The false vs. a declined false, 'INSUFFICIENT_FUNDS' lets a caller tell "no money system" apart from "the transaction was refused".

Storage

The bridge owns the connection; you own your tables. Wait for storage, then run your own SQL:

AddEventHandler('BenzyBridge:StorageReady', function()
if exports.BenzyBridge:GetStorageMode() ~= 'database' then return end -- file mode: use your own files
exports.BenzyBridge:Execute([[
CREATE TABLE IF NOT EXISTS myscript_data (
license VARCHAR(60) NOT NULL PRIMARY KEY,
value BIGINT NOT NULL DEFAULT 0
)]])
end)

-- later, in a thread
local row = exports.BenzyBridge:Single('SELECT value FROM myscript_data WHERE license = ?', { license })

The storage helpers (Query, Single, Scalar, Insert, Execute, GetStorageMode, IsDatabase, IsStorageReady, WaitForStorage) are server-only and are called on the bridge resource. Storage settles asynchronously, so wait for BenzyBridge:StorageReady as above — or block on WaitForStorage(), which yields until it's ready and returns the mode — before your first query.


Custom notification / prompt systems

For a notification or draw-text prompt system the bridge has no built-in provider for, set that concern to 'custom' and fill its Config.Custom hook. Each call is resolved in order: a shared function Fn, then a Resource + Export, then an Event — the first one filled in is used.

-- BenzyBridge config.lua
Config.Notify = 'custom'
Config.Custom.Notify = { Resource = 'my_notify', Export = 'ShowNotification', Event = '' }
-- called as exports.my_notify:ShowNotification(kind, title, message, duration)

The prompt (draw-text) hook works the same way, called with (a, b) — the key/label and the message. Unlike a toast, a prompt stays up until something takes it down, so it also takes a hide side (HideFnHideExportHideEvent, on the same Resource), called with no arguments:

Config.DrawText = 'custom'
Config.Custom.DrawText = { Resource = 'my_prompt', Export = 'ShowText', HideExport = 'HideText', Event = '', HideEvent = '' }
-- shown as exports.my_prompt:ShowText(a, b)
-- hidden as exports.my_prompt:HideText()

Benzy.Hide() and Benzy.HideAll() both call the hide side; the hook has no per-prompt id, so it clears whatever your system is showing. Benzy.IsShown() reports whether the bridge currently has a custom prompt up.

Fill the hide side. Leave it blank and a custom prompt can be shown but never dismissed — it stays on screen.

Leave a field blank to skip it. If nothing resolves — or your hook throws — the bridge renders the built-in instead, so a message is never silently lost, and warns once in the console naming the hook, so a mistyped resource or export name shows up instead of erroring on every call.

A custom money system is not registered here — it needs an op-map, so it's added as an adapter (next section).


Custom money systems (adapters)

Money has many operations that differ per script, so a custom money system is added as a small adapter — a table that maps the bridge's ops onto that script's real exports. Register it under the name custom (the only name accepted; anything else is refused with a console warning), then point Config.Money at 'custom'.

The registry is runtime state, so a bridge restart clears it. Register from a function you call in both places — on your own start, and whenever the bridge asks:

-- server, in your money script (or a small companion resource)
local function registerAdapter()
exports.BenzyBridge:RegisterMoneyProvider('custom', {
GetCash = function(id) return exports.my_money:getBalance(id, 'cash') end,
GetBank = function(id) return exports.my_money:getBalance(id, 'bank') end,
CanAfford = function(id, account, amount) return exports.my_money:getBalance(id, account) >= amount end,
AddMoney = function(id, account, amount, reason) return exports.my_money:give(id, account, amount) end,
RemoveMoney = function(id, account, amount, reason) return exports.my_money:take(id, account, amount) end,
-- implement the ops your server actually uses; every op you leave out returns false, 'UNSUPPORTED'
})
end

AddEventHandler('BenzyBridge:RequestMoneyProvider', registerAdapter) -- the bridge (re)started
CreateThread(function() -- ...and if it is already up
if GetResourceState('BenzyBridge') == 'started' then registerAdapter() end
end)

Each side keeps its own adapter, so if your UI reads balances through the bridge, register a client one too. Client ops are self-based and read-only — money is always mutated server-side:

-- client, only if something reads balances client-side
exports.BenzyBridge:RegisterMoneyProvider('custom', {
GetCash = function() return exports.my_money:getMyCash() end,
GetBank = function() return exports.my_money:getMyBank() end,
})
-- BenzyBridge config.lua
Config.Money = 'custom'

Now every Benzy.AddMoney(...) across every resource flows into your money script — with no consumer changes. You only implement the ops you need; the rest report UNSUPPORTED automatically, and an op that throws is caught and reported the same way — but warns once naming the op, so a broken adapter doesn't read as a missing feature. Selecting 'custom' with nothing registered prints a startup error rather than failing silently; debugbridge also shows whether an adapter is registered.