Skip to Content
Open in ChatGPTOpen in Claude

ESX Structure and xPlayer API

ESX  exposes a shared ESX table on both sides. On the server, that table hands you an xPlayer object per connected player.

Getting the ESX object

The cleanest way is to let ESX inject it, by adding its import file to your fxmanifest.lua:

shared_script '@es_extended/imports.lua'

That gives you a global ESX on both sides with no boilerplate. If you would rather fetch it explicitly:

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

Older tutorials use an esx:getSharedObject event callback. That pattern is obsolete — the export is synchronous and is what imports.lua itself calls.

xPlayer

local xPlayer = ESX.GetPlayerFromId(source) xPlayer = { source = 1, identifier = 'license:...', name = 'John Doe', group = 'user', accounts = { { name = 'money', money = 500 }, { name = 'bank', money = 5000 }, { name = 'black_money', money = 0 }, }, job = { name = 'police', label = 'Police', grade = 1, grade_name = 'officer', grade_label = 'Officer', grade_salary = 100, }, inventory = {}, coords = { x = 0.0, y = 0.0, z = 0.0, heading = 0.0 }, }

The key difference from QBCore : ESX identifies players by identifier, not by a generated citizen id, and jobs live in the jobs and job_grades database tables rather than in a Lua file.

For release-specific player data fields, consult the ESX core source .

Money and accounts

⚠️

xPlayer.addMoney() and xPlayer.removeMoney() still exist, but the official documentation marks them as backwards-compatibility shims. Use the account functions in new code.

local xPlayer = ESX.GetPlayerFromId(source) -- Preferred xPlayer.addAccountMoney('money', 100, 'paycheck') xPlayer.removeAccountMoney('bank', 50, 'atm-fee') xPlayer.setAccountMoney('black_money', 0, 'wipe') local account = xPlayer.getAccount('bank') print(account.money) -- Backwards-compatible shims for the 'money' account only xPlayer.addMoney(100, 'paycheck') xPlayer.removeMoney(50, 'fine')

Always pass the reason string. It is written to the money logs.

Inventory and items

xPlayer.addInventoryItem('bread', 1) xPlayer.removeInventoryItem('bread', 1) xPlayer.setInventoryItem('water', 5) local item = xPlayer.getInventoryItem('bread') if xPlayer.canCarryItem('bread', 1) then xPlayer.addInventoryItem('bread', 1) end

canCarryItem respects the weight limit set by xPlayer.setMaxWeight. Adding items without checking it is how servers end up with players carrying impossible loads.

Jobs, groups and metadata

xPlayer.setJob('police', 1) local job = xPlayer.getJob() xPlayer.setGroup('admin') local group = xPlayer.getGroup() xPlayer.setMeta('licence_points', 3) local points = xPlayer.getMeta('licence_points') xPlayer.clearMeta('licence_points')

setGroup writes the ACE principal used for permissions. See Permissions.

ESX core source (xPlayer implementation) → 

Server-side helpers

ESX.RegisterServerCallback('myScript:getData', function(source, cb, arg) cb(someValue) end) ESX.RegisterCommand('heal', 'admin', function(xPlayer, args) -- ... end, false) local players = ESX.GetExtendedPlayers() local jobs = ESX.GetJobs() local items = ESX.GetItems()
ℹ️

Do not read ESX.Players, ESX.Jobs or ESX.Items directly. They are empty at server start and changes do not propagate to importing resources. The documentation is explicit about using the getter functions instead.

ESX core source (server functions) → 

Key events

EventSideFires when
esx:playerLoadedBothA character finishes loading
esx:playerDroppedServerA player disconnects
esx:onPlayerDeathBothA player dies
esx:setJobBothA player’s job changes
esx:setAccountMoneyClientAn account balance changes
esx:addInventoryItemClientAn item is added
esx:removeInventoryItemClientAn item is removed
⚠️

Never trust a client-triggered event carrying money, items or a job name. Fetch the player server-side with ESX.GetPlayerFromId(source) and validate there. See FiveM events.

ESX core source (client and server events) → 

Next steps