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:

text
SCRIPT ERROR: @my_script/server/main.lua:23: attempt to index a nil value (local 'xPlayer')
lua
local xPlayer = ESX.GetPlayerFromId(source)
xPlayer.addMoney(100)   -- xPlayer is nil

ESX.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:

lua
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:

lua
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:

lua
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.

lua
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:

lua
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 where ESX.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 is GetPlayerServerId(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 source equal to 0, 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.
lua
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:

lua
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:

lua
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:

lua
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.

Scripts that skip this problem

Shop CreatorBuild a shop in under a minute — owners, employees, vaults and robberies included.View script →Pawn Shop AppA player-to-player pawn market inside lb-phone.View script →Drug Dealer AppStreet sales as an lb-phone app: zones, NPC buyers, levels and police alerts.View script →

Keep reading