# Server Events (/docs/es_extended/events/server)

These are the `esx:` events you meet on the server. Most of them are **local server events** that `es_extended` fires so other resources can react, for example when a player loads, changes job or gets money. A second group are **net events sent by clients**, which you must treat as untrusted input.

## Overview [#overview]

| | |
|---|---|
| Resource | `es_extended` |
| Side | Server |
| Listen with | `AddEventHandler` (local events), `RegisterNetEvent` (events sent by clients) |

| Marker | Meaning |
|---|---|
| **Listen only** | Fired by `es_extended`. Do not trigger it yourself, other scripts would act on fake data. |
| **Internal** | Used by ESX for its own flow. Do not trigger or override. |
| **Client-sent (untrusted)** | Arrives from a client through `TriggerServerEvent`. Anyone can send any arguments. |

<Callout type="error" title="Security">
Never give money, items or permissions inside a handler for an event that a client can send. The client-sent events below carry whatever the client wants. Always look the player up with `ESX.GetPlayerFromId(source)` and verify on the server.
</Callout>

## Player lifecycle [#player-lifecycle]

### esx:playerLoaded [#esxplayerloaded]

**Listen only.** Fired after a character is loaded and the xPlayer object exists. Also fires for every character switch with multicharacter.

| Name | Type | Description |
|---|---|---|
| `playerId` | `number` | Server id. |
| `xPlayer` | `table` | The [xPlayer](/docs/es_extended/server/xplayer) object. |
| `isNew` | `boolean` | `true` if the character was just created. |

```lua title="server/main.lua"
AddEventHandler('esx:playerLoaded', function(playerId, xPlayer, isNew)
    if isNew then
        xPlayer.addInventoryItem('phone', 1)
        xPlayer.showNotification('Welcome to the city!')
    end
end)
```

### esx:playerDropped [#esxplayerdropped]

**Listen only.** Fired when an xPlayer is removed: disconnect, logout or a stale session cleanup. Fired before the final save, so the xPlayer still exists.

| Name | Type | Description |
|---|---|---|
| `playerId` | `number` | Server id. |
| `reason` | `string` | Drop reason. `esx_player_logout` for logouts. |

```lua
AddEventHandler('esx:playerDropped', function(playerId, reason)
    local xPlayer = ESX.GetPlayerFromId(playerId)
    if xPlayer then
        print(('%s left (%s)'):format(xPlayer.getName(), reason))
    end
end)
```

### esx:playerSaved [#esxplayersaved]

**Listen only.** Fired after a player's row was written to the database (autosave every 10 minutes, on drop and manual saves).

| Name | Type | Description |
|---|---|---|
| `playerId` | `number` | Server id. |
| `xPlayer` | `table` | The saved xPlayer. |

### esx:playerLogout [#esxplayerlogout]

**Server-only trigger.** Trigger it from server code to log a player out of the current character. ESX saves and removes the xPlayer, then sends `esx:onPlayerLogout` to the client. Registered with `AddEventHandler`, so clients cannot trigger it.

| Name | Type | Description |
|---|---|---|
| `playerId` | `number` | Server id. |
| `cb` | `function?` | Called when the logout is done. |

```lua
TriggerEvent('esx:playerLogout', playerId, function()
    print(('Player %s is back at character selection'):format(playerId))
end)
```

### esx:onPlayerJoined [#esxonplayerjoined]

**Internal.** Starts loading the player.

- Without multicharacter: client-sent net event with no arguments. ESX ignores it if the player is already loaded or loading.
- With multicharacter: local server event `(playerId, char, data)` triggered by the multicharacter resource.

### esx:onPlayerDropped [#esxonplayerdropped]

**Internal.** Local server event `(playerId, reason, cb)` that runs the ESX drop and save routine. Use `esx:playerLogout` instead.

### esx:onPlayerSpawn [#esxonplayerspawn]

**Client-sent (untrusted).** Sent by the client after the first spawn. ESX only sets `xPlayer.spawned = true`.

## Jobs and groups [#jobs-and-groups]

### esx:setJob [#esxsetjob]

**Listen only.** Fired after `xPlayer.setJob`.

| Name | Type | Description |
|---|---|---|
| `playerId` | `number` | Server id. |
| `job` | `table` | New job (`name`, `label`, `grade`, `grade_name`, `grade_label`, `grade_salary`, ...). |
| `lastJob` | `table` | Previous job. |

```lua
AddEventHandler('esx:setJob', function(playerId, job, lastJob)
    if job.name == 'police' and lastJob.name ~= 'police' then
        exports.my_radio:GiveChannelAccess(playerId, 1)
    end
end)
```

### esx:jobDataRefreshed [#esxjobdatarefreshed]

**Listen only.** Fired when a player's job data was rebuilt after the job definition changed (for example after `ESX.RefreshJob`).

| Name | Type | Description |
|---|---|---|
| `playerId` | `number` | Server id. |
| `job` | `table` | Updated job. |
| `lastJob` | `table` | Job before the refresh. |

### esx:setGroup [#esxsetgroup]

**Listen only.** Fired after `xPlayer.setGroup`.

| Name | Type | Description |
|---|---|---|
| `playerId` | `number` | Server id. |
| `group` | `string` | New group. |
| `lastGroup` | `string` | Previous group. |

### esx:jobsRefreshed [#esxjobsrefreshed]

**Listen only.** No arguments. Fired after all jobs were loaded from the database (`ESX.RefreshJobs`).

### esx:jobRefreshed [#esxjobrefreshed]

**Listen only.** Fired after one job was reloaded with `ESX.RefreshJob`.

| Name | Type | Description |
|---|---|---|
| `jobName` | `string` | Job name. |
| `job` | `table` | Job data with `grades`. |

### esx:jobRemoved [#esxjobremoved]

**Listen only.** Fired when a refreshed job no longer exists or has no grades. Players with that job are set to `unemployed`.

| Name | Type | Description |
|---|---|---|
| `jobName` | `string` | Removed job. |
| `reason` | `string` | `missing job` or `no job grades found`. |

### esx:jobCreated [#esxjobcreated]

**Listen only.** Fired after `ESX.CreateJob` created a job.

| Name | Type | Description |
|---|---|---|
| `name` | `string` | Job name. |
| `job` | `table` | The new entry in `ESX.Jobs`. |

## Money and inventory [#money-and-inventory]

### esx:setAccountMoney / esx:addAccountMoney / esx:removeAccountMoney [#esxsetaccountmoney--esxaddaccountmoney--esxremoveaccountmoney]

**Listen only.** Fired by `xPlayer.setAccountMoney`, `addAccountMoney` and `removeAccountMoney` (also with ox_inventory).

| Name | Type | Description |
|---|---|---|
| `playerId` | `number` | Server id. |
| `accountName` | `string` | `money`, `bank`, `black_money`, ... |
| `money` | `number` | For `set`: the new balance. For `add` / `remove`: the amount changed. |
| `reason` | `string?` | Reason passed to the xPlayer method. |

```lua
AddEventHandler('esx:addAccountMoney', function(playerId, account, amount, reason)
    if account == 'black_money' and amount >= 100000 then
        print(('[audit] %s received %s dirty money (%s)'):format(playerId, amount, reason or 'no reason'))
    end
end)
```

### esx:onAddInventoryItem / esx:onRemoveInventoryItem [#esxonaddinventoryitem--esxonremoveinventoryitem]

**Listen only.** Built-in inventory only. Fired after `xPlayer.addInventoryItem` / `removeInventoryItem`.

| Name | Type | Description |
|---|---|---|
| `playerId` | `number` | Server id. |
| `itemName` | `string` | Item name. |
| `count` | `number` | The **new total** count of the item, not the amount added. |

### esx:playerInventoryCleared / esx:playerLoadoutCleared [#esxplayerinventorycleared--esxplayerloadoutcleared]

**Listen only.** Fired by the `/clearinventory` and `/clearloadout` admin commands.

| Name | Type | Description |
|---|---|---|
| `xPlayer` | `table` | The target xPlayer (not an id). |

## Vehicles [#vehicles]

### esx:createdExtendedVehicle / esx:deletedExtendedVehicle [#esxcreatedextendedvehicle--esxdeletedextendedvehicle]

**Listen only.** Fired when an ExtendedVehicle object is created or deleted.

| Name | Type | Description |
|---|---|---|
| `vehicle` | `table` | The ExtendedVehicle object (has `plate`). |

### esx:changedExtendedVehiclePlate [#esxchangedextendedvehicleplate]

**Listen only.** Fired after an ExtendedVehicle plate change.

| Name | Type | Description |
|---|---|---|
| `newPlate` | `string` | New plate. |
| `oldPlate` | `string` | Previous plate. |

## Events sent by clients [#events-sent-by-clients]

### Handled by es_extended [#handled-by-es_extended]

These net events are handled by the built-in inventory (not registered when `Config.CustomInventory` is set). ESX validates and rate-limits them. Do not register your own handlers that trust their arguments.

| Event | Arguments | What ESX does |
|---|---|---|
| `esx:giveInventoryItem` | `target, itemType, itemName, itemCount` | Gives an item, money, weapon or ammo to a nearby player. |
| `esx:removeInventoryItem` | `itemType, itemName, itemCount` | Drops something on the ground as a pickup. |
| `esx:useItem` | `itemName` | Uses an item via `ESX.UseItem` if the player has it. |
| `esx:onPickup` | `pickupId` | Picks up a pickup within 5 units. |
| `esx:requestPickups` | none | Sends nearby pickups to the client. |
| `esx:updateWeaponAmmo` | `weaponName, ammoCount` | Syncs ammo. Only lowers the stored ammo, never raises it. |

### Not handled by es_extended [#not-handled-by-es_extended]

The client sends these so other resources can use them. ESX itself registers no server handler. **All arguments are client-controlled.**

#### esx:onPlayerDeath [#esxonplayerdeath]

| Field of `data` | Type | Description |
|---|---|---|
| `victimCoords` | `table` | `{ x, y, z }` rounded to 1 decimal. |
| `killedByPlayer` | `boolean` | Whether another player caused the death. |
| `deathCause` | `number` | Weapon hash from `GetPedCauseOfDeath`. |
| `killerCoords` | `table?` | Killer position (player kills only). |
| `distance` | `number?` | Distance to the killer (player kills only). |
| `killerServerId` | `number?` | Killer server id (player kills only). |
| `killerClientId` | `number?` | Killer client player index (player kills only). |

```lua title="server/main.lua"
RegisterNetEvent('esx:onPlayerDeath', function(data)
    local victim = source
    if type(data) ~= 'table' or not data.killedByPlayer then return end

    local killer = tonumber(data.killerServerId)
    if not killer or not ESX.GetPlayerFromId(killer) then return end

    local distance = #(GetEntityCoords(GetPlayerPed(victim)) - GetEntityCoords(GetPlayerPed(killer)))
    if distance > 300.0 then return end -- claim does not match server state

    print(('Player %s was killed by %s'):format(victim, killer))
end)
```

#### Vehicle events [#vehicle-events]

| Event | Arguments |
|---|---|
| `esx:enteringVehicle` | `plate, seat, netId` |
| `esx:enteringVehicleAborted` | none |
| `esx:enteredVehicle` | `plate, seat, displayName, netId` |
| `esx:exitedVehicle` | `plate, seat, displayName, netId` |

```lua
RegisterNetEvent('esx:enteredVehicle', function(plate, seat, displayName, netId)
    local vehicle = NetworkGetEntityFromNetworkId(netId)
    if vehicle == 0 or GetVehiclePedIsIn(GetPlayerPed(source), false) ~= vehicle then return end

    print(('%s entered %s (%s) in seat %s'):format(source, displayName, plate, seat))
end)
```

`netId` is the local entity handle when the vehicle is not networked, so always check the result of `NetworkGetEntityFromNetworkId`.

## Deprecated and removed [#deprecated-and-removed]

| Event | Status |
|---|---|
| `esx:getSharedObject` | Deprecated. Still answers for old scripts. Use `exports['es_extended']:getSharedObject()`. |
| `esx:playerJumping` | Removed. Not fired in 1.16. |

## Related [#related]

- [Client events](/docs/es_extended/events/client)
- [xPlayer](/docs/es_extended/server/xplayer)
- [Server functions](/docs/es_extended/server/functions)
- [Callbacks](/docs/es_extended/client/modules/callback)
