Files
2026-08-06 08:23:14 +02:00

9.7 KiB

ESX Events & Callbacks

Server Callbacks

Server callbacks allow the client to request data from the server.

Registering a Server Callback

-- SERVER
ESX.RegisterServerCallback('myResource:getPlayerData', function(source, cb, additionalParam)
    local xPlayer = ESX.GetPlayerFromId(source)
    if not xPlayer then return cb(nil) end
    
    local data = {
        money = xPlayer.getMoney(),
        job = xPlayer.job.name,
        grade = xPlayer.job.grade,
        param = additionalParam
    }
    
    cb(data)
end)

Calling a Server Callback

-- CLIENT
ESX.TriggerServerCallback('myResource:getPlayerData', function(data)
    if data then
        print('Money:', data.money)
        print('Job:', data.job)
    end
end, 'extraParam')

Common Patterns

-- Check if player can afford something
ESX.RegisterServerCallback('shop:canAfford', function(source, cb, itemName)
    local xPlayer = ESX.GetPlayerFromId(source)
    if not xPlayer then return cb(false) end
    
    local price = Config.Items[itemName].price
    cb(xPlayer.getMoney() >= price)
end)

-- CLIENT usage
ESX.TriggerServerCallback('shop:canAfford', function(canAfford)
    if canAfford then
        -- Show buy menu
    else
        lib.notify({title = 'Shop', description = 'Not enough money', type = 'error'})
    end
end, 'bread')

Client Callbacks

WARNING: Client callbacks should NEVER be used for sensitive operations! Client can fake any data.

Registering a Client Callback

-- CLIENT
ESX.RegisterClientCallback('myResource:getVehicleModel', function(cb, vehicle)
    local model = GetEntityModel(vehicle)
    cb(model)
end)

Calling a Client Callback (Server)

-- SERVER
ESX.TriggerClientCallback(source, 'myResource:getVehicleModel', function(model)
    print('Vehicle model:', model)
end, vehicleNetId)

ESX Events

Client Events

esx:playerLoaded

Fired when player's character loads.

-- CLIENT
AddEventHandler('esx:playerLoaded', function(playerData)
    ESX.PlayerData = playerData
    print('Player loaded:', playerData.firstName, playerData.lastName)
    
    -- Initialize your resource
    startClientScripts()
end)

esx:updatePlayerData

Fired when any PlayerData is updated.

-- CLIENT
AddEventHandler('esx:updatePlayerData', function(key, value)
    ESX.PlayerData[key] = value
    
    if key == 'job' then
        print('Job changed to:', value.name)
    elseif key == 'money' then
        print('Money updated:', value)
    end
end)

esx:setJob

Fired when player's job changes.

-- CLIENT
AddEventHandler('esx:setJob', function(job)
    ESX.PlayerData.job = job
    print('New job:', job.name, 'Grade:', job.grade)
    
    -- Start/stop job-specific systems
    if job.name == 'police' then
        startPoliceBlips()
    else
        stopPoliceBlips()
    end
end)

esx:setAccountMoney

Fired when player's account money changes.

-- CLIENT
AddEventHandler('esx:setAccountMoney', function(account)
    print('Account updated:', account.name, account.money)
end)

esx:addInventoryItem

Fired when player receives an item.

-- CLIENT
AddEventHandler('esx:addInventoryItem', function(item, count)
    print('Received:', count, 'x', item.label)
end)

esx:removeInventoryItem

Fired when player loses an item.

-- CLIENT
AddEventHandler('esx:removeInventoryItem', function(item, count)
    print('Removed:', count, 'x', item.label)
end)

esx:onPlayerDeath

Fired when player dies.

-- CLIENT
AddEventHandler('esx:onPlayerDeath', function(data)
    print('Player died')
    print('Killer:', data.killerServerId)
    
    -- Respawn logic
end)

esx:onPlayerSpawn

Fired when player spawns.

-- CLIENT
AddEventHandler('esx:onPlayerSpawn', function()
    print('Player spawned')
end)

esx:playerPedChanged

Fired when player ped changes (e.g., after model change).

-- CLIENT
local playerPed = PlayerPedId()

AddEventHandler('esx:playerPedChanged', function(newPed)
    playerPed = newPed
    print('Ped changed:', newPed)
end)

Server Events

esx:onPlayerJoined

Fired when player connects (before character selection).

-- SERVER
AddEventHandler('esx:onPlayerJoined', function()
    local _source = source
    print('Player connected:', _source)
end)

esx:playerLoaded

Fired when player's character loads.

-- SERVER
AddEventHandler('esx:playerLoaded', function(playerId, xPlayer)
    print('Player loaded:', xPlayer.getName())
    
    -- Give welcome bonus
    if xPlayer.getMeta('firstTime') == nil then
        xPlayer.addMoney(5000, 'Welcome bonus')
        xPlayer.setMeta('firstTime', false)
    end
end)

esx:playerDropped

Fired when player disconnects.

-- SERVER
AddEventHandler('esx:playerDropped', function(playerId, reason)
    print('Player ' .. playerId .. ' left:', reason)
end)

esx:setJob

Fired when player's job changes (server-side).

-- SERVER
AddEventHandler('esx:setJob', function(playerId, job, lastJob)
    local xPlayer = ESX.GetPlayerFromId(playerId)
    if not xPlayer then return end
    
    print(xPlayer.getName() .. ' changed from ' .. lastJob.name .. ' to ' .. job.name)
    
    -- Log job change
    MySQL.Async.execute('INSERT INTO job_changes (identifier, old_job, new_job) VALUES (@identifier, @old, @new)', {
        ['@identifier'] = xPlayer.identifier,
        ['@old'] = lastJob.name,
        ['@new'] = job.name
    })
end)

Secure Net Events

Use SecureNetEvent for client events that should only be triggered by server.

Registering Secure Net Event

-- CLIENT
ESX.SecureNetEvent('myResource:giveReward', function(amount, reason)
    -- Only server can trigger this
    print('Received reward:', amount, reason)
    
    lib.notify({
        title = 'Reward',
        description = 'You received $' .. amount .. ' for ' .. reason,
        type = 'success'
    })
end)

Triggering Secure Net Event

-- SERVER
local xPlayer = ESX.GetPlayerFromId(source)
xPlayer.triggerEvent('myResource:giveReward', 500, 'completing mission')

Custom Events

Triggering Client Event from Server

-- SERVER
local xPlayer = ESX.GetPlayerFromId(source)
xPlayer.triggerEvent('myResource:openMenu', menuData)

-- Or using TriggerClientEvent
TriggerClientEvent('myResource:openMenu', source, menuData)

-- Or for multiple players
local officers = ESX.GetExtendedPlayers('job', 'police')
for i, xPlayer in ipairs(officers) do
    xPlayer.triggerEvent('myResource:alert', 'Code 3 at Legion Square')
end

Triggering Server Event from Client

-- CLIENT
TriggerServerEvent('myResource:buyItem', 'bread')

Receiving Custom Events

-- CLIENT
RegisterNetEvent('myResource:openMenu')
AddEventHandler('myResource:openMenu', function(menuData)
    -- Open menu with data
end)

-- SERVER
RegisterNetEvent('myResource:buyItem')
AddEventHandler('myResource:buyItem', function(itemName)
    local xPlayer = ESX.GetPlayerFromId(source)
    if not xPlayer then return end
    
    -- Validate and process purchase
    local price = Config.Items[itemName].price
    if xPlayer.getMoney() >= price then
        xPlayer.removeMoney(price, 'Bought ' .. itemName)
        xPlayer.addInventoryItem(itemName, 1)
    end
end)

Best Practices

  1. Always validate server-side:

    -- BAD: Trust client data
    RegisterNetEvent('shop:buy')
    AddEventHandler('shop:buy', function(price)
        local xPlayer = ESX.GetPlayerFromId(source)
        xPlayer.removeMoney(price) -- Client controls price!
    end)
    
    -- GOOD: Server validates
    RegisterNetEvent('shop:buy')
    AddEventHandler('shop:buy', function(itemName)
        local xPlayer = ESX.GetPlayerFromId(source)
        if not xPlayer then return end
    
        local price = Config.Items[itemName].price
        if xPlayer.getMoney() >= price then
            xPlayer.removeMoney(price, 'Bought ' .. itemName)
            xPlayer.addInventoryItem(itemName, 1)
        end
    end)
    
  2. Use callbacks for data requests:

    -- GOOD: Use callback
    ESX.TriggerServerCallback('shop:canAfford', function(canAfford)
        if canAfford then
            -- Do something
        end
    end, 'bread')
    
    -- BAD: Use event
    TriggerServerEvent('shop:checkAfford', 'bread')
    RegisterNetEvent('shop:affordResult')
    AddEventHandler('shop:affordResult', function(canAfford)
        -- Client can fake this event
    end)
    
  3. Use SecureNetEvent for important client events:

    -- CLIENT
    ESX.SecureNetEvent('police:giveArmor', function()
        SetPedArmour(PlayerPedId(), 100)
    end)
    
    -- SERVER (validated)
    local xPlayer = ESX.GetPlayerFromId(source)
    if xPlayer.job.name == 'police' then
        xPlayer.triggerEvent('police:giveArmor')
    end
    
  4. Always check for nil:

    RegisterNetEvent('myResource:action')
    AddEventHandler('myResource:action', function()
        local xPlayer = ESX.GetPlayerFromId(source)
        if not xPlayer then return end
    
        -- Safe to use xPlayer
    end)
    
  5. Use proper event naming:

    -- GOOD: Descriptive names
    'myResource:openShopMenu'
    'myResource:buyItem'
    'myResource:sellItem'
    
    -- BAD: Vague names
    'myResource:event1'
    'myResource:action'
    'openMenu'
    
  6. Listen to ESX events for state changes:

    -- CLIENT: React to job changes
    AddEventHandler('esx:setJob', function(job)
        if job.name == 'police' then
            startPoliceFeatures()
        else
            stopPoliceFeatures()
        end
    end)
    
    -- Check on resource start too
    CreateThread(function()
        while not ESX.IsPlayerLoaded() do Wait(100) end
    
        if ESX.PlayerData.job.name == 'police' then
            startPoliceFeatures()
        end
    end)