Cube Scripts

Frameworks

How the ESX, QBCore and standalone bridges work, and how to add your own.

Every bridge lives in server/bridge/ and is listed in escrow_ignore, so you can read and edit all of it.

How detection works#

config.lua
Config.Framework = "auto"

On "auto", the resource checks which core resource is started:

config.lua
DetectFramework = function()
    if Config.Framework ~= "auto" then return end

    if GetResourceState("es_extended") == "started" then
        Config.Framework = "esx"
    elseif GetResourceState("qb-core") == "started" then
        Config.Framework = "qbcore"
    else
        Config.Framework = "standalone"
    end
end

Each bridge then guards itself on the first line, so only one ever loads:

server/bridge/esx.lua
local ESX = Config.Framework == "esx" and exports['es_extended']:getSharedObject()
if not ESX then return end

With Config.Debug on you will see which one loaded:

code
[DEBUG] Framework: ESX
[DEBUG] ESX file loaded successfully!

What a bridge provides#

Each bridge fills the same framework table:

FunctionReturnsUsed for
GetPlayer(src)player objectEvery other function
GetPlayerName(player)stringCharacter name in the list
GetPlayerIdentifier(player)stringLogging
GetPlayerGroup(player)stringPermission check
GetPlayerJob(player)stringAvailable for your own use
GetPlayers()tableAvailable for your own use
HasPermission(src)booleanGating every entry point
GetCharacterName(src)string or nilThe name shown in the panel
Note

GetCharacterName is the one the interface uses. When it returns nil - always, on standalone - the panel falls back to the Steam/FiveM account name.

Adding your own framework#

1

Create the bridge file#

Copy server/bridge/standalone.lua to server/bridge/myframework.lua and guard it on your framework name:

server/bridge/myframework.lua
local Core = Config.Framework == "myframework" and exports['my-core']:GetCore()
if not Core then return end

Debug("MyFramework file loaded successfully!")
framework = {}

framework.GetPlayer = function(src)
    return Core.GetPlayer(src)
end

framework.GetCharacterName = function(src)
    local player = framework.GetPlayer(src)
    if not player then return nil end

    return player.name
end

framework.HasPermission = function(src)
    local player = framework.GetPlayer(src)
    if not player then return false end

    for _, allowed in ipairs(Config.Groups.myframework) do
        if player.rank == allowed then return true end
    end

    return false
end

server/bridge/*.lua is a wildcard in the manifest, so the new file loads with no manifest change.

2

Add the detection#

config.lua
DetectFramework = function()
    if Config.Framework ~= "auto" then return end

    if GetResourceState("my-core") == "started" then
        Config.Framework = "myframework"
        Debug("Framework: MyFramework")
    elseif GetResourceState("es_extended") == "started" then
        Config.Framework = "esx"
    -- ...
    end
end
3

Add your groups#

config.lua
Config.Groups = {
    esx = { "admin", "superadmin" },
    qbcore = { "admin", "god" },
    myframework = { "staff", "owner" },
}

Skipping the bridge entirely#

If your permissions come from somewhere else - a database, a Discord role, your own admin resource - you do not need a bridge at all. Edit HasPermission in server/function.lua:

server/function.lua
HasPermission = function(src)
    if not src or src == 0 then return true end

    return exports['my-admin']:isStaff(src)
end
Warning

HasPermission gates every command, every NUI callback and the player list. If it returns true for everyone, every player can watch every other player.

Keep Config.Framework = "standalone" in that case, so no bridge loads and the panel falls back to account names.