DEV Community

xFiveM Shop
xFiveM Shop

Posted on

Writing One FiveM Script That Works on Both QBCore and ESX (A Simple Bridge Pattern)

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
Enter fullscreen mode Exit fullscreen mode
fx_version 'cerulean'
game 'gta5'
lua54 'yes'

server_scripts {
    'bridge/server.lua',
    'server/main.lua',
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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 cash and bank; ESX uses money and bank accounts. 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.lua with 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)