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)
endcanCarryItem 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
| Event | Side | Fires when |
|---|---|---|
esx:playerLoaded | Both | A character finishes loading |
esx:playerDropped | Server | A player disconnects |
esx:onPlayerDeath | Both | A player dies |
esx:setJob | Both | A player’s job changes |
esx:setAccountMoney | Client | An account balance changes |
esx:addInventoryItem | Client | An item is added |
esx:removeInventoryItem | Client | An 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
- ESX resources — what ships with the framework
- Writing your first script
- Lua scripting basics →