attempt to index a nil value (local 'xPlayer'): ESX fix
xPlayer is nil in your ESX script? Why ESX.GetPlayerFromId(source) returns nil: player not loaded, source lost after Wait, string ids, playerDropped. With guard patterns.
The server console shows:
SCRIPT ERROR: @my_script/server/main.lua:23: attempt to index a nil value (local 'xPlayer')local xPlayer = ESX.GetPlayerFromId(source)
xPlayer.addMoney(100) -- xPlayer is nilESX.GetPlayerFromId found no player for that id and returned nil, and the next line indexed it. The call is fine. The id you gave it does not match a loaded ESX player. Here are the usual reasons and how to guard against each.
1. The player is not loaded yet
A player is connected to the server before ESX has finished loading their character. During that gap, and while a multicharacter screen is open, ESX.GetPlayerFromId(source) returns nil.
This hits code that runs early, such as the generic FiveM events playerJoining or playerConnecting, or a thread that starts the moment your resource starts.
Use the ESX event that fires when the player is ready, on the server:
AddEventHandler('esx:playerLoaded', function(playerId, xPlayer, isNew)
print(('%s is loaded'):format(xPlayer.getName()))
end)On the client, the same event name tells your client script that the player can be used:
RegisterNetEvent('esx:playerLoaded', function(xPlayer)
PlayerLoaded = true
end)If your script can be restarted while players are online, that event has already fired for them. On start, loop over the players ESX already has:
CreateThread(function()
for _, playerId in ipairs(GetPlayers()) do
local xPlayer = ESX.GetPlayerFromId(tonumber(playerId))
if xPlayer then
-- set up the player
end
end
end)2. source changed after a Wait
source is a special global that FiveM sets for the event that is running. If you wait inside the handler, another event can run in the meantime and source no longer belongs to your player.
RegisterNetEvent('my_script:buy', function(item)
Wait(500)
local xPlayer = ESX.GetPlayerFromId(source) -- source may be someone else, or invalid
end)Store it in a local before anything else:
RegisterNetEvent('my_script:buy', function(item)
local src = source
Wait(500)
local xPlayer = ESX.GetPlayerFromId(src)
if not xPlayer then return end
end)The same applies inside a callback or a function that you pass source to later: pass the stored value, not the global.
3. The id is wrong
ESX.GetPlayerFromId needs the server id as a number of a player who is online.
- A string id. Command arguments are text.
ESX.GetPlayerFromId(args[1])can return nil whereESX.GetPlayerFromId(tonumber(args[1]))works. - The wrong id from the client. On the client,
PlayerId()is a local index, not the server id. The server id isGetPlayerServerId(PlayerId()). Sending the wrong one gives a player that does not exist, or the wrong player. - The console. A command typed in the server console has
sourceequal to0, which is not a player. - An id from an identifier lookup.
ESX.GetPlayerFromIdentifier(identifier)only finds players who are online. For an offline player, read the database instead, see the oxmysql queries guide.
RegisterCommand('givecash', function(source, args)
local target = tonumber(args[1])
local amount = tonumber(args[2])
if not target or not amount then
return print('Usage: /givecash [id] [amount]')
end
local xTarget = ESX.GetPlayerFromId(target)
if not xTarget then
return print('Player not found or not loaded')
end
xTarget.addMoney(amount)
end, true)Do not trust an id sent from the client for something that matters. Use source, which cannot be faked, as explained in securing server events.
4. The player has left
If the player disconnects while your code waits, for example in a long loop or a timer, GetPlayerFromId returns nil when it finally runs. This is a normal case, not a bug, and it needs the same guard.
Inside playerDropped the situation is less clear, because ESX also handles that event to save and remove the player, and the order of handlers is not something to rely on. ESX Legacy also triggers its own esx:playerDropped event, but check that your version has it. The most robust approach is to keep the data yourself while the player is online:
local sessions = {}
AddEventHandler('esx:playerLoaded', function(playerId, xPlayer)
sessions[playerId] = {
identifier = xPlayer.identifier,
job = xPlayer.job.name,
}
end)
AddEventHandler('playerDropped', function()
local data = sessions[source]
if data then
print(('%s left, job %s'):format(data.identifier, data.job))
sessions[source] = nil
end
end)The guard pattern
Start every server handler that uses a player the same way:
RegisterNetEvent('my_script:sell', function(item, count)
local src = source
local xPlayer = ESX.GetPlayerFromId(src)
if not xPlayer then return end
-- from here xPlayer is safe to use
xPlayer.addMoney(100)
end)The same check goes inside server callbacks registered with ESX.RegisterServerCallback, where the first argument is the player's source:
ESX.RegisterServerCallback('my_script:getJob', function(source, cb)
local xPlayer = ESX.GetPlayerFromId(source)
if not xPlayer then return cb(nil) end
cb(xPlayer.job.name)
end)Check the client side too: it must accept a nil answer from the callback.
Tip: if the error is
attempt to index a nil value (global 'ESX')instead, the problem is the ESX object, not the player. See ESX is nil: fixing esx:getSharedObject.
Checklist
| Symptom | Fix |
|---|---|
| Nil right after the player joins | Wait for esx:playerLoaded instead of playerJoining |
Nil after a Wait |
Store local src = source first and use src |
| Nil from a command | Convert the argument with tonumber; source is 0 in the console |
| Nil with an id sent by the client | Use source; on the client the server id is GetPlayerServerId(PlayerId()) |
Nil in playerDropped |
Keep the data you need in your own table while the player is online |
| Nil for an offline player | GetPlayerFromId only finds online players; query the database |
| Any nil | Add if not xPlayer then return end at the top of the handler |
Quick answers
Why is xPlayer nil in ESX?
ESX.GetPlayerFromId(source) returns nil when ESX has no loaded player for that id. The player may not have finished loading, the id may be wrong or a string, or the player already left.
Should I check xPlayer in every event?
Yes. Any server event can be triggered with a player who is not loaded, so start the handler with if not xPlayer then return end. It costs nothing and prevents the error.
Can I use xPlayer inside playerDropped?
It is not safe to rely on it, because ESX may already have removed the player. Keep the data you need in your own table while the player is online, and read it from there.


