A lot of FiveM servers run either QBCore or ESX, and a lot of script authors end up maintaining two copies of the same resource. That gets painful fast: every bug fix has to be done twice and the two versions drift apart. A small "bridge" file solves most of this. Here is the pattern I use.
The idea
Your script logic should never call QBCore.Functions... or ESX.GetPlayerFromId... directly. Instead it calls your own small API (Bridge.GetPlayer, Bridge.AddMoney, Bridge.GetJob), and one file decides which framework is underneath.
my_script/
├── fxmanifest.lua
├── bridge/
│ └── server.lua
└── server/
└── main.lua
fx_version 'cerulean'
game 'gta5'
lua54 'yes'
server_scripts {
'bridge/server.lua',
'server/main.lua',
}
The bridge loads first, so main.lua can use it straight away.
Detecting the framework
-- bridge/server.lua
Bridge = {}
local framework
if GetResourceState('qb-core') == 'started' then
framework = 'qb'
Bridge.Core = exports['qb-core']:GetCoreObject()
elseif GetResourceState('es_extended') == 'started' then
framework = 'esx'
Bridge.Core = exports['es_extended']:getSharedObject()
else
print('^1[my_script] No supported framework found (qb-core or es_extended)^0')
end
Bridge.Framework = framework
GetResourceState is a native, so this works without any extra dependency. Remember to start your framework before this resource in server.cfg so the check sees it as started.
Wrapping the common calls
function Bridge.GetPlayer(src)
if framework == 'qb' then
return Bridge.Core.Functions.GetPlayer(src)
elseif framework == 'esx' then
return Bridge.Core.GetPlayerFromId(src)
end
end
function Bridge.AddMoney(src, amount)
local player = Bridge.GetPlayer(src)
if not player then return false end
if framework == 'qb' then
player.Functions.AddMoney('cash', amount)
else
player.addMoney(amount)
end
return true
end
function Bridge.GetJob(src)
local player = Bridge.GetPlayer(src)
if not player then return nil end
if framework == 'qb' then
return player.PlayerData.job.name
else
return player.job.name
end
end
Using it in your actual script
-- server/main.lua
RegisterNetEvent('my_script:payout', function()
local src = source
if Bridge.GetJob(src) ~= 'mechanic' then
return
end
Bridge.AddMoney(src, 250)
end)
That file has no idea which framework is running, and that is the whole point. When a new framework version renames something, you fix it in one place.
Things to watch out for
- Validate on the server. The example above checks the job server-side before paying out. Never trust a client event to tell you how much money to give.
-
Account names differ. QBCore uses
cashandbank; ESX usesmoneyandbankaccounts. Keep that mapping inside the bridge. - Inventory is its own problem. Many servers use ox_inventory or qb-inventory regardless of framework, so treat inventory as a second bridge rather than tying it to QBCore or ESX.
-
Client side too. If your script needs player data on the client, write a small
bridge/client.luawith the same approach.
Why this matters for open source scripts
When the source is open, server owners can read the bridge, see exactly how it maps to their framework, and patch it themselves if they run a fork. That is much easier than debugging an escrowed resource you cannot open. If you want to see more examples of this style, xFiveM Shop publishes open source FiveM scripts with QBCore and ESX support, and the setup steps are in the xfivem.shop installation guide.
Wrapping up
A bridge file is maybe 60 lines, and it saves you from maintaining two versions of every script. Start with player, money and job, then add inventory and notifications as you need them.
How do you handle multi-framework support in your resources? Let me know in the comments.
Top comments (0)