EndCore Framework

Running QB and ESX scripts

How EndCore's QB-Core, QBX and ESX bridges let existing scripts run, what works, the known limits, and how to turn the bridges off.

Many FiveM scripts are written for QB-Core, QBX or ESX. EndCore can run a lot of them without changes: en-core pretends to be those frameworks, answering their exports and player functions with EndCore's own data.

The bridges are a convenience, not a full emulation. Simple scripts (shops, jobs, small utilities) usually work. Scripts that lean on framework-specific systems EndCore doesn't have, such as gangs, societies or billing, need changes.

How the bridges work

  1. en-core declares provides { 'qb-core', 'qbx_core', 'es_extended' }, so a script whose manifest depends on qb-core passes the dependency check.
  2. en-core answers exports under those resource names. exports['qb-core']:GetCoreObject() and exports['es_extended']:getSharedObject() both resolve to en-core, even though neither resource is installed.
  3. The objects they return are backed by EndCore. A QB player object is the EndCore player, so Player.Functions.AddMoney is EndCore's AddMoney.

Each bridge only turns on if the real framework isn't running. When a bridge is active, the console prints a line such as:

text
[EndCore] qb-core compatibility bridge active

If the real resource is installed, the console prints qb-core is running; skipping its compatibility bridge instead.

Before you add a script

  • Remove the real framework. Don't run qb-core, qbx_core or es_extended alongside EndCore.
  • Define its items in en-inventory. QBShared.Items is empty, and ESX item calls go through EndCore's inventory, so every item the script uses must exist in en-inventory/data/items.lua. See Add an item.
  • Check its other dependencies. EndCore does not provide ox_lib, ox_inventory or ox_target, so scripts built on those won't run through the bridges.
  • Start it after EndCore in server.cfg.
cfg
ensure en-ui
ensure en-core
ensure [encore]

# third-party scripts after EndCore
ensure some-qb-script

QB-Core

A typical QB script keeps working:

lua
local QBCore = exports['qb-core']:GetCoreObject()

RegisterNetEvent('some-script:payout', function()
    local Player = QBCore.Functions.GetPlayer(source)
    if not Player then return end
    Player.Functions.AddMoney('bank', 200, 'qb-script payout')
end)

What works

AreaProvided
ExportsGetCoreObject, GetSharedObject, AddJob, UpdateJob, AddGang, UpdateGang
Shared dataQBShared.Jobs (from en-core/shared/jobs.lua), Vehicles, Weapons, helper functions RandomStr, RandomInt, Trim, Round
Server playersQBCore.Players, Functions.GetPlayer, GetPlayerByCitizenId, GetOfflinePlayerByCitizenId, GetPlayers, GetQBPlayers, GetPlayerByPhone
Player functionsEndCore's own player object: money, jobs, metadata, items and saving all work
CallbacksFunctions.CreateCallback, TriggerClientCallback, TriggerCallback
Usable itemsFunctions.CreateUseableItem, routed to EndCore's inventory
PermissionsFunctions.HasPermission, GetPermission (god, admin, mod or user by ACE), IsOptin, Kick
CommandsCommands.Add. Any permission other than user becomes the group.<permission> principal.
ClientQBCore.PlayerData kept in sync, GetPlayerData, Notify, CreateClientCallback, TriggerCallback, Progressbar (mapped to the EndCore progress bar), world, drawing and animation helpers
Client eventsQBCore:Client:OnPlayerLoaded, QBCore:Player:SetPlayerData, QBCore:Client:OnPlayerUnload, QBCore:Client:OnJobUpdate, QBCore:Client:OnGangUpdate

Gangs become groups

EndCore has no gangs. Its player groups stand in: PlayerData.gang is filled from the player's group.

PlayerPlayerData.gang
In a group{ name = 'group_<id>', label = <group name>, isboss = <is leader>, grade = { name = <rank>, level = <grade> } }
Not in a group{ name = 'none', label = 'No Gang', ... }

Functions.SetGang exists so scripts don't crash, but it does nothing and returns false.

QBX

SideExports under qbx_core
ServerGetPlayer, GetPlayerByCitizenId, GetOfflinePlayer, GetPlayers, Login, Logout, SaveAllPlayers, GetJobs, GetGangs (always empty), GetVehicles, GetWeapons, GetPlayerCharacters, DeleteCharacter
ClientGetPlayerData, IsLoggedIn, GetJobs, GetGangs (always empty), GetVehicles, GetWeapons. The global QBX.PlayerData is kept in sync.

Many of QBX's other exports are not provided, such as setting jobs or adding money by source, or its group helpers. Scripts that call them fail on that call.

Note

The QBX DeleteCharacter export deletes the character row directly and does not remove the character from its group first. Prefer exports['en-core']:DeleteCharacter in your own code.

ESX

lua
local ESX = exports['es_extended']:getSharedObject()

RegisterNetEvent('some-script:reward', function()
    local xPlayer = ESX.GetPlayerFromId(source)
    if not xPlayer then return end
    xPlayer.addAccountMoney('money', 50)   -- 'money' is EndCore cash
    xPlayer.addInventoryItem('bandage', 1)
end)

What works

AreaProvided
ExportgetSharedObject on server and client
SharedESX.Jobs (converted, with salary from the job's payment), ESX.Vehicles, ESX.Weapons, ESX.GetConfig()
ServerGetPlayerFromId, GetPlayerFromIdentifier (by license, online first, then the most recently played character), GetExtendedPlayers, GetPlayers, RegisterServerCallback, TriggerClientCallback, RegisterUsableItem, RegisterCommand, SavePlayer, SavePlayers
xPlayer moneygetMoney, setMoney, addMoney, removeMoney, getAccount, getAccounts, addAccountMoney, removeAccountMoney, setAccountMoney. ESX money is EndCore cash; bank is bank.
xPlayer inventorygetInventoryItem, addInventoryItem, removeInventoryItem, setInventoryItem, canCarryItem, canSwapItem
xPlayer othersource, identifier, name, job, getName, getIdentifier, setCoords, getCoords, kick, setJob, getJob, notifications, getMeta, setMeta, getMetas, setMetas, triggerEvent
ClientESX.PlayerData, PlayerLoaded, GetPlayerData, IsPlayerLoaded, SetPlayerData, notifications, TriggerServerCallback, RegisterClientCallback, ESX.Game.* and ESX.Streaming.* helpers
Eventsesx:playerLoaded, esx:playerDropped, esx:setJob, esx:setAccountMoney, esx:onPlayerLogout

ESX.RegisterCommand groups other than user become group.<name> principals, and player arguments are converted to xPlayer objects.

Known limits

LimitAffectsWhat to do
No QB server eventsScripts listening for QBCore:Server:OnPlayerLoaded and similarListen for encore:server:onPlayerLoaded instead. See Events.
Server-side QB notifications don't displayQBCore.Functions.Notify(source, ...) on the serverReplace with TriggerClientEvent('en-ui:notify', source, { description = text, type = 'success' }). Client-side Notify works.
No gangsGang scriptsRewrite around groups.
QBShared.Items is emptyScripts that read item labels or weights from itRead exports['en-inventory']:GetItemDefinition(name) instead.
No real weight in ESXgetWeight always returns 0; getMaxWeight returns 24000Weight checks always pass. en-inventory still refuses items that don't fit, so check return values.
No ESX societies, billing, licences, properties or pickupsScripts built on thoseNot supported. ESX.UseItem and ESX.CreatePickup only warn.
ESX grade_name and grade_labelJob displaysApproximations of EndCore job grades
Partial QBX export setScripts using the wider QBX APIReplace those calls with en-core exports
No paychecksScripts expecting salary payoutsen-core stores job pay but has no paycheck loop

Usable items and consumption

QB CreateUseableItem and ESX RegisterUsableItem both route to encore.inventory.registerUsable. en-inventory only consumes an item if the handler returns true. QB and ESX handlers normally remove the item themselves and return nothing, so they won't be double-charged.

Porting a script properly

Bridges are a good way to try a script. For scripts you plan to keep, porting to the EndCore API is worth it: you get the survival stats, groups, content registry and design system, and nothing depends on bridge coverage. Common swaps:

Framework callEndCore equivalent
QBCore.Functions.GetPlayer(src) / ESX.GetPlayerFromId(src)exports['en-core']:GetPlayer(src)
Player.Functions.AddMoney('cash', n) / xPlayer.addMoney(n)exports['en-core']:AddMoney(src, 'cash', n, reason)
QBCore.Functions.CreateCallback / ESX.RegisterServerCallbackencore.callback.register
QBCore.Functions.CreateUseableItem / ESX.RegisterUsableItemencore.inventory.registerUsable
QBCore.Commands.Add / ESX.RegisterCommandencore.addCommand
QBCore.Functions.Notify / ESX.ShowNotificationencore.notify
QBCore.Functions.Progressbarencore.progress

See Writing a resource. If you have a large script library to move over, Fxora takes porting and custom development work.

Turning the bridges off

If you run only EndCore-native resources, you can switch all three bridges off. Add this to server.cfg above ensure en-core:

cfg
setr encore:disablebridge true

Use setr, not set. The client-side bridges read the convar too, and only setr replicates it to players.

With the bridges off, exports['qb-core'], exports['qbx_core'] and exports['es_extended'] calls fail, so any remaining framework script stops working. The global QBShared table is still defined, because the QB shared file always loads.