Plenty of FiveM scripts are written for QB-Core, Qbox or ESX. en-core includes compatibility bridges that let many of those scripts run on EndCore without edits. A QB script that calls exports['qb-core']:GetCoreObject() gets an object backed by EndCore's own players, money and jobs.
The bridges are for convenience, not full emulation. Simple scripts that read player data, move money and register usable items usually work. Scripts that depend on gangs, societies, billing or framework-specific events will need changes.
How it works
Three pieces work together:
- The manifest provides the names. en-core's
fxmanifest.luahasprovides { 'qb-core', 'qbx_core', 'es_extended' }, so a third-party manifest withdependency 'qb-core'is satisfied. - Exports are answered under the other resource's name. FiveM resolves
exports['qb-core']:GetCoreObject()by firing an internal event named__cfx_export_qb-core_GetCoreObject. The bridge listens for that event and hands back its own function. This is exactly whatexports('GetCoreObject', fn)does internally, just pointed at another resource's name. The call reaches en-core even though qb-core is not installed. - Each bridge checks whether it should run. If the real framework is started or starting, its bridge stays off and the console prints
<resource> is running; skipping its compatibility bridge.
The library exposes the export trick as encore.provideExport(resource, name, fn):
-- what encore.provideExport does
AddEventHandler(('__cfx_export_%s_%s'):format('qb-core', 'GetCoreObject'), function(setCB)
setCB(function()
return QBCore
end)
end)The bridges never call exports() for these names. That would register the function under en-core itself and overwrite en-core's own exports, because Qbox uses the same export names, such as GetPlayer.
When a bridge is active, the console prints [EndCore] qb-core compatibility bridge active (and likewise for qbx_core and es_extended).
QB-Core
Shared
The global QBShared is always defined, even when the bridge is disabled.
| Field | Contents |
|---|---|
Jobs | EndCore's shared/jobs.lua |
Gangs | { none = { label = 'No Gang', ... } } |
Vehicles, Weapons | EndCore's shared tables |
Items | Empty. Items live in en-inventory. |
GroupAsGang(group) | Converts an EndCore group to a QB gang table |
ForceJobUpdate, ForceGangUpdate | Also available as UpdateJob and UpdateGang. They fire qb-core:server:jobUpdate and qb-core:server:gangUpdate. |
RandomStr, RandomInt, Trim, Round | Helpers |
Server
Exports under qb-core: GetCoreObject, GetSharedObject, AddJob, UpdateJob, AddGang, UpdateGang. The global QBCoreObject is also set.
The QBCore object has:
| Area | Provided |
|---|---|
| Data | Config (en-core's server config), Shared, and Players, a live view of the online registry |
| Player lookups | Functions.GetPlayer, GetPlayerByCitizenId, GetOfflinePlayerByCitizenId, GetPlayers, GetQBPlayers (array), GetPlayerByPhone |
| Callbacks | Functions.CreateCallback, TriggerClientCallback, and TriggerCallback as an alias of TriggerClientCallback |
| Items | Functions.CreateUseableItem, routed to encore.inventory.registerUsable |
| Admin | Functions.Kick, HasPermission (ACE check, string or list), GetPermission (god, admin, mod or user via ACE), IsOptin (ACE admin), Notify |
| Jobs and gangs | Functions.AddJob, UpdateJob, AddGang, UpdateGang |
| Commands | Commands.Add(name, help, arguments, argsrequired, callback, permission) |
Commands.Add splits the typed line into an array of strings. Any permission other than 'user' becomes restricted = 'group.<permission>'.
The player object is en-core's own player, so Player.Functions.AddMoney, SetJob, SetMetadata and the rest work unchanged. Player.PlayerData.gang is filled from the player's group on login and whenever the group changes:
| Situation | PlayerData.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', ... } |
Player.Functions.SetGang exists so scripts don't error, but it does nothing. It returns false and warns once.
Client
Exports under qb-core: GetCoreObject, GetSharedObject.
| Area | Provided |
|---|---|
| Data | QBCore.PlayerData, kept in sync |
| Functions | GetPlayerData(cb?), Notify(text or { text, caption }, type, length) (primary becomes inform), CreateClientCallback, TriggerCallback |
| World helpers | SpawnVehicle, DeleteVehicle, GetPlate, GetClosestPlayer, GetPlayersFromCoords, GetClosestVehicle, GetClosestPed, GetVehicles, GetPeds |
| Drawing and animation | DrawText, DrawText3D, RequestAnimDict, PlayAnim, LoadModel |
| Progress | Progressbar(...), mapped onto encore.progress |
Local events fired for QB scripts: QBCore:Client:OnPlayerLoaded, QBCore:Player:SetPlayerData, QBCore:Client:OnPlayerUnload, QBCore:Client:OnJobUpdate(job) and QBCore:Client:OnGangUpdate(gang).
QB limitations
- No QB server events are fired.
QBCore:Server:OnPlayerLoadedand similar never trigger. Useencore:server:onPlayerLoadedinstead. - Server-side
QBCore.Functions.NotifysendsQBCore:Notifyto the client, but no client handler is registered, so those notifications do not show. - No gangs beyond the group mapping above.
QBShared.Itemsis empty. Read item data from en-inventory.
Qbox (QBX)
Server exports under qbx_core
| Export | Notes |
|---|---|
GetPlayer, GetPlayerByCitizenId, GetOfflinePlayer, GetPlayers | en-core's player objects |
Login(source, citizenid?, newData?) | Returns boolean. With no citizenid, creates a character from newData and logs in. |
Logout, SaveAllPlayers | |
GetJobs, GetVehicles, GetWeapons | EndCore's shared tables |
GetGangs | Returns {} |
GetPlayerCharacters(license) | |
DeleteCharacter(citizenid) | Deletes the row directly. Unlike en-core's own DeleteCharacter, it does not remove the character from its group first. |
Client exports under qbx_core
GetPlayerData, IsLoggedIn, GetJobs, GetGangs (returns {}), GetVehicles, GetWeapons. The global QBX.PlayerData is kept in sync.
QBX limitations
Only the exports listed above exist. Qbox's other exports, such as setting jobs or money by source and its group functions, are not provided. Use en-core's own exports for those.
ESX
Shared
ESX.Jobs (converted, with salary taken from payment), ESX.Gangs = {}, ESX.Vehicles, ESX.Weapons and ESX.GetConfig(). GetConfig returns a fixed table: its StartingAccountMoney is always 500 cash and 1000 bank and does not read your config.
The export es_extended:getSharedObject works on both server and client.
Server
| Function | Notes |
|---|---|
ESX.GetPlayerFromId(source) | Returns an xPlayer |
ESX.GetPlayerFromIdentifier(license) | The online player first, otherwise the most recently updated character as an offline xPlayer |
ESX.GetExtendedPlayers(), ESX.GetPlayers() | |
ESX.RegisterServerCallback, ESX.TriggerClientCallback | |
ESX.RegisterUsableItem | Routed to encore.inventory.registerUsable |
ESX.RegisterCommand(name, group, cb, allowConsole, suggestion) | Groups other than user become group.<name> principals. player arguments are converted to xPlayer objects. |
ESX.SavePlayer, ESX.SavePlayers | |
ESX.UseItem, ESX.CreatePickup | Stubs that only print a warning |
xPlayer
| Area | Provided |
|---|---|
| Fields | source, identifier (the license), license, name, job (ESX shape), coords |
| Identity and position | triggerEvent, getIdentifier, getName, setCoords, getCoords, kick |
| Money | getMoney, setMoney, addMoney, removeMoney, getAccount, getAccounts, addAccountMoney, removeAccountMoney, setAccountMoney |
| Inventory | getInventoryItem, addInventoryItem, removeInventoryItem, setInventoryItem, canCarryItem, canSwapItem |
| Weight | getWeight always returns 0, getMaxWeight returns 24000 |
| Job | setJob, getJob |
| Notifications | showNotification, showAdvancedNotification, showHelpNotification |
| Metadata | getMeta, setMeta, getMetas, setMetas |
ESX's money account is EndCore's cash (the cash item when cash is an item). ESX's bank is EndCore's bank.
Events translated for ESX scripts:
esx:playerLoaded(source, xPlayer)fires on the server and is sent to the client.esx:playerDropped(source)fires when a character unloads.esx:setJobis sent to the client on job changes.esx:setAccountMoney({ name, money })is sent to the client with the new balance.
Client
| Area | Provided |
|---|---|
| Data | ESX.PlayerData, ESX.PlayerLoaded, ESX.GetPlayerData() (accounts money and bank), ESX.IsPlayerLoaded, ESX.SetPlayerData |
| Notifications | ESX.ShowNotification, ESX.ShowAdvancedNotification, ESX.ShowHelpNotification, ESX.UI.ShowInventoryItemNotification |
| Callbacks | ESX.TriggerServerCallback, ESX.RegisterClientCallback |
ESX.Game | GetClosestPlayer, GetPlayersInArea, GetClosestVehicle, GetVehiclesInArea, GetClosestObject, SpawnVehicle, DeleteVehicle, Teleport, IsSpawnPointClear |
ESX.Streaming | RequestModel, RequestStreamedTextureDict, RequestStreamSet, RequestAnimDict, RequestAnimSet, RequestNamedPtfxAsset |
Local events fired: esx:playerLoaded, esx:onPlayerLogout, esx:setJob, esx:setAccountMoney.
ESX limitations
- No societies, billing, licences, properties or pickups.
- No real inventory weight.
grade_nameandgrade_labelon the job are approximations.canSwapItemis approximate.
Examples
An unmodified QB script:
local QBCore = exports['qb-core']:GetCoreObject()
RegisterNetEvent('myscript:server:payout', function()
local Player = QBCore.Functions.GetPlayer(source)
if Player then
Player.Functions.AddMoney('bank', 200, 'qb-script payout')
end
end)An unmodified ESX script:
local ESX = exports['es_extended']:getSharedObject()
ESX.RegisterUsableItem('bandage', function(source)
local xPlayer = ESX.GetPlayerFromId(source)
xPlayer.removeInventoryItem('bandage', 1)
xPlayer.addAccountMoney('money', 50) -- 'money' is EndCore cash
end)Disabling the bridges
To turn off all three bridges, add this to server.cfg:
setr encore:disablebridge trueUse setr, not set. The client bridges read the convar too, and only setr replicates it to clients. The QBShared global is still defined when bridges are disabled.
You do not need to disable a bridge to run the real framework: if qb-core, qbx_core or es_extended is actually started, en-core skips that bridge on its own.
For a step-by-step guide to porting scripts, see Running QB and ESX scripts.