Frameworks
Supporting several frameworks with a bridge
Write FiveM resources that run on ESX, QBCore, Qbox or standalone: detecting the framework with GetResourceState, a bridge layer with one interface (player, money, job, notify, items), file layout, config overrides, testing each framework, and the common pitfalls.
Overview
If you sell or share scripts, supporting only one framework cuts your audience in half. The answer used by most successful resources is a bridge: your script talks to a small interface you define, and a thin file per framework implements it. The script’s logic never mentions ESX or QBCore.
Detecting the framework
local function detect()
if Config.Framework and Config.Framework ~= 'auto' then return Config.Framework end
if GetResourceState('qbx_core') == 'started' then return 'qbx' end
if GetResourceState('qb-core') == 'started' then return 'qb' end
if GetResourceState('es_extended') == 'started' then return 'esx' end
return 'standalone'
end
Framework = detect()Check Qbox before QBCore: Qbox ships a QBCore compatibility layer, so testing for qb-core first could pick the bridge path you did not intend. Make sure your resource starts after the framework (list it as a dependency or order it in server.cfg).
Define one interface
| Function | Returns / does |
|---|---|
Bridge.getIdentifier(src) | Character ID (citizenid / identifier / charId) |
Bridge.getJob(src) | { name, grade } |
Bridge.addMoney(src, account, amount) | true/false |
Bridge.removeMoney(src, account, amount) | true/false |
Bridge.notify(src, text, type) | Shows a notification |
Bridge.addItem / Bridge.removeItem | Through the active inventory |
Two bridge implementations
if Framework ~= 'esx' then return end
local ESX = exports.es_extended:getSharedObject()
Bridge = {}
function Bridge.getIdentifier(src)
local x = ESX.GetPlayerFromId(src)
return x and x.identifier
end
function Bridge.getJob(src)
local x = ESX.GetPlayerFromId(src)
return x and { name = x.job.name, grade = x.job.grade }
end
function Bridge.addMoney(src, account, amount)
local x = ESX.GetPlayerFromId(src)
if not x then return false end
if account == 'cash' then x.addMoney(amount) else x.addAccountMoney(account, amount) end
return true
end
function Bridge.notify(src, text)
TriggerClientEvent('esx:showNotification', src, text)
endif Framework ~= 'qbx' then return end
Bridge = {}
function Bridge.getIdentifier(src)
local p = exports.qbx_core:GetPlayer(src)
return p and p.PlayerData.citizenid
end
function Bridge.getJob(src)
local p = exports.qbx_core:GetPlayer(src)
return p and { name = p.PlayerData.job.name, grade = p.PlayerData.job.grade.level }
end
function Bridge.addMoney(src, account, amount)
return exports.qbx_core:AddMoney(src, account, amount)
end
function Bridge.notify(src, text, kind)
exports.qbx_core:Notify(src, text, kind or 'inform')
endshared_scripts {
'config.lua',
'shared/framework.lua',
}
server_scripts {
'bridge/esx/server.lua',
'bridge/qb/server.lua',
'bridge/qbx/server.lua',
'bridge/standalone/server.lua',
'server/main.lua',
}Logic that never mentions a framework
RegisterNetEvent('fishmarket:sell', function()
local src = source
local job = Bridge.getJob(src)
if job and job.name == 'police' then
return Bridge.notify(src, 'Police cannot sell fish', 'error')
end
-- count and remove fish through the inventory bridge, then:
Bridge.addMoney(src, 'cash', 45)
end)Inventories are a second axis
Framework and inventory vary independently: an ESX server may run ox_inventory, a QBCore server qb-inventory or a community ox_inventory fork (official ox_inventory supports ESX, Qbox, ox_core and ND_Core). Bridge items separately from the framework — see inventory integration for developers.
Pitfalls
- Grades: ESX uses a number, QBCore/Qbox a table with
level— normalise in the bridge. - Account names differ (
moneyvscashon ESX) — map them. - Player-loaded events differ — expose one
Bridge.onPlayerLoadedon the client. - Never claim support you have not tested.
Frequently asked questions
How do I make a FiveM script work on ESX and QBCore?
Write your logic against a small bridge interface and implement that interface once per framework, choosing the bridge at runtime with GetResourceState.
How do I detect which framework is running?
GetResourceState('es_extended') == 'started', and the same for qb-core and qbx_core.
Why check Qbox before QBCore?
Qbox provides a QBCore compatibility layer, so a QBCore check alone can match a Qbox server.
What is a standalone script?
One that works without any framework, often with an optional bridge to use framework features when present.
Need this built, not just explained?
Ten years of FiveM work, from Lua to NUI
Custom resources, React NUI, ESX / QBCore / Qbox integration, OneSync performance audits and security reviews — plus the websites and SEO around your server brand.
Related guides
- FrameworksWriting your first ESX Legacy scriptImport ESX with shared_script '@es_extended/imports.lua' (or exports.es_extended:getSharedObject()). On the server, ESX.GetPlayerFromId(source) returns xPlayer, which handles money (addMoney, addAccountMoney('bank', …)), items (addInventoryItem, getInventoryItem), and the job (xPlayer.job.name, .grade). Register usable items with ESX.RegisterUsableItem and commands with ESX.RegisterCommand. On the client use ESX.PlayerData and the esx:playerLoaded and esx:setJob events.
- FrameworksWriting your first QBCore scriptGet the core with local QBCore = exports['qb-core']:GetCoreObject(). On the server, QBCore.Functions.GetPlayer(source) returns the Player: read Player.PlayerData (citizenid, job, money) and change state with Player.Functions.AddMoney('cash', n), AddItem, RemoveItem, SetJob. Register usable items with QBCore.Functions.CreateUseableItem, commands with QBCore.Commands.Add. On the client use QBCore.Functions.GetPlayerData() and the QBCore:Client:OnPlayerLoaded / OnJobUpdate events.
- FrameworksWriting scripts for Qbox (qbx_core)On the server call qbx_core exports: exports.qbx_core:GetPlayer(source), AddMoney(source, 'cash', amount, reason), SetJob(source, job, grade), HasGroup(source, filter) and Notify(source, text, type). On the client, add '@qbx_core/modules/playerdata.lua' to get QBX.PlayerData, kept in sync automatically. Items and inventories go through ox_inventory; UI through ox_lib. A bridge keeps most QBCore scripts working.