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
en-coredeclaresprovides { 'qb-core', 'qbx_core', 'es_extended' }, so a script whose manifest depends onqb-corepasses the dependency check.en-coreanswers exports under those resource names.exports['qb-core']:GetCoreObject()andexports['es_extended']:getSharedObject()both resolve toen-core, even though neither resource is installed.- The objects they return are backed by EndCore. A QB player object is the EndCore player, so
Player.Functions.AddMoneyis EndCore'sAddMoney.
Each bridge only turns on if the real framework isn't running. When a bridge is active, the console prints a line such as:
[EndCore] qb-core compatibility bridge activeIf 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.Itemsis empty, and ESX item calls go through EndCore's inventory, so every item the script uses must exist inen-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.
ensure en-ui
ensure en-core
ensure [encore]
# third-party scripts after EndCore
ensure some-qb-scriptQB-Core
A typical QB script keeps working:
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
| Area | Provided |
|---|---|
| Exports | GetCoreObject, GetSharedObject, AddJob, UpdateJob, AddGang, UpdateGang |
| Shared data | QBShared.Jobs (from en-core/shared/jobs.lua), Vehicles, Weapons, helper functions RandomStr, RandomInt, Trim, Round |
| Server players | QBCore.Players, Functions.GetPlayer, GetPlayerByCitizenId, GetOfflinePlayerByCitizenId, GetPlayers, GetQBPlayers, GetPlayerByPhone |
| Player functions | EndCore's own player object: money, jobs, metadata, items and saving all work |
| Callbacks | Functions.CreateCallback, TriggerClientCallback, TriggerCallback |
| Usable items | Functions.CreateUseableItem, routed to EndCore's inventory |
| Permissions | Functions.HasPermission, GetPermission (god, admin, mod or user by ACE), IsOptin, Kick |
| Commands | Commands.Add. Any permission other than user becomes the group.<permission> principal. |
| Client | QBCore.PlayerData kept in sync, GetPlayerData, Notify, CreateClientCallback, TriggerCallback, Progressbar (mapped to the EndCore progress bar), world, drawing and animation helpers |
| Client events | QBCore: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.
| Player | 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', ... } |
Functions.SetGang exists so scripts don't crash, but it does nothing and returns false.
QBX
| Side | Exports under qbx_core |
|---|---|
| Server | GetPlayer, GetPlayerByCitizenId, GetOfflinePlayer, GetPlayers, Login, Logout, SaveAllPlayers, GetJobs, GetGangs (always empty), GetVehicles, GetWeapons, GetPlayerCharacters, DeleteCharacter |
| Client | GetPlayerData, 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.
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
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
| Area | Provided |
|---|---|
| Export | getSharedObject on server and client |
| Shared | ESX.Jobs (converted, with salary from the job's payment), ESX.Vehicles, ESX.Weapons, ESX.GetConfig() |
| Server | GetPlayerFromId, GetPlayerFromIdentifier (by license, online first, then the most recently played character), GetExtendedPlayers, GetPlayers, RegisterServerCallback, TriggerClientCallback, RegisterUsableItem, RegisterCommand, SavePlayer, SavePlayers |
| xPlayer money | getMoney, setMoney, addMoney, removeMoney, getAccount, getAccounts, addAccountMoney, removeAccountMoney, setAccountMoney. ESX money is EndCore cash; bank is bank. |
| xPlayer inventory | getInventoryItem, addInventoryItem, removeInventoryItem, setInventoryItem, canCarryItem, canSwapItem |
| xPlayer other | source, identifier, name, job, getName, getIdentifier, setCoords, getCoords, kick, setJob, getJob, notifications, getMeta, setMeta, getMetas, setMetas, triggerEvent |
| Client | ESX.PlayerData, PlayerLoaded, GetPlayerData, IsPlayerLoaded, SetPlayerData, notifications, TriggerServerCallback, RegisterClientCallback, ESX.Game.* and ESX.Streaming.* helpers |
| Events | esx: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
| Limit | Affects | What to do |
|---|---|---|
| No QB server events | Scripts listening for QBCore:Server:OnPlayerLoaded and similar | Listen for encore:server:onPlayerLoaded instead. See Events. |
| Server-side QB notifications don't display | QBCore.Functions.Notify(source, ...) on the server | Replace with TriggerClientEvent('en-ui:notify', source, { description = text, type = 'success' }). Client-side Notify works. |
| No gangs | Gang scripts | Rewrite around groups. |
QBShared.Items is empty | Scripts that read item labels or weights from it | Read exports['en-inventory']:GetItemDefinition(name) instead. |
| No real weight in ESX | getWeight always returns 0; getMaxWeight returns 24000 | Weight checks always pass. en-inventory still refuses items that don't fit, so check return values. |
| No ESX societies, billing, licences, properties or pickups | Scripts built on those | Not supported. ESX.UseItem and ESX.CreatePickup only warn. |
ESX grade_name and grade_label | Job displays | Approximations of EndCore job grades |
| Partial QBX export set | Scripts using the wider QBX API | Replace those calls with en-core exports |
| No paychecks | Scripts expecting salary payouts | en-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 call | EndCore 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.RegisterServerCallback | encore.callback.register |
QBCore.Functions.CreateUseableItem / ESX.RegisterUsableItem | encore.inventory.registerUsable |
QBCore.Commands.Add / ESX.RegisterCommand | encore.addCommand |
QBCore.Functions.Notify / ESX.ShowNotification | encore.notify |
QBCore.Functions.Progressbar | encore.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:
setr encore:disablebridge trueUse 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.
Related
- Compatibility bridges: the full reference.
- Core overview and Player API: the native EndCore API.