# esx_lib (/docs/core/esx_lib)

esx_lib is the official ESX utility library. It exposes a global `xLib` table that lazily loads modules (callbacks, player cache, points, NUI helpers, streaming, validation, rate limiting and more) into any resource that includes `@esx_lib/imports.lua`. es_extended itself is built on top of it, so every ESX Legacy server needs esx_lib started first.

## Overview [#overview]

| Fact | Value |
| --- | --- |
| Resource name | `esx_lib` (the loader hardcodes this name, do not rename the folder) |
| Version | `1.0.0` (`legacyversion '1.16.3'` in fxmanifest) |
| Side | Shared (client and server modules) |
| Dependencies | None. `oxmysql` is only needed by the server-side `schema` and catalog modules, which run inside es_extended. |
| Used by | es_extended, esx_identity, esx_inventory, esx_menu_default, esx_menu_dialog, esx_multicharacter, esx_progressbar, esx_skin, esx_whitelist |
| Global | `xLib` (with fields `name`, `side`, `debug`) |

Parts of esx_lib are adapted from [ox_lib](https://github.com/overextended/ox_lib) by Linden (LGPL-3.0). Those files carry the original notice in the source.

## Installation and load order [#installation-and-load-order]

esx_lib ships in the `[core]` folder of esx_core. Start it before es_extended:

```bash title="server.cfg"
ensure oxmysql
ensure esx_lib
ensure es_extended
```

To use xLib in your own resource, add the import as a shared script and declare the dependency:

```lua title="fxmanifest.lua"
fx_version 'cerulean'
game 'gta5'
lua54 'yes'

shared_scripts {
    '@esx_lib/imports.lua',
    '@es_extended/imports.lua',
}

client_scripts { 'client/*.lua' }
server_scripts { 'server/*.lua' }

dependencies {
    'es_extended',
    'esx_lib',
}
```

### How lazy loading works [#how-lazy-loading-works]

`imports.lua` creates the `xLib` table with an `__index` metamethod. The first time you read `xLib.<name>`, it loads `imports/<name>/shared.lua` plus `imports/<name>/client.lua` or `imports/<name>/server.lua` from esx_lib and runs them inside your resource. Modules you never touch are never loaded.

If no module folder exists for a name, xLib returns a proxy that calls `exports.esx_lib:<name>(...)`. This is how functions that must live in one central place (callback validation, pub/sub registry, `triggerClientEvent`) are shared by every resource.

<Callout type="warn" title="Global require is replaced">
`imports.lua` sets `require = xLib.require` in your resource. Plain `require('module')` therefore loads files from your resource through `LoadResourceFile`, not from the standard Lua package path. See [require](#require-load-and-loadjson).
</Callout>

## Module summary [#module-summary]

| Module | Side | Purpose |
| --- | --- | --- |
| `addKeybind` | Client | Register key mappings with press and release handlers |
| `blips` | Client | Create and remove map blips from an options table |
| `cache` | Client | Cached `ped`, `vehicle`, `seat`, `weapon`, `playerId`, `serverId`, `coords` |
| `callback` | Client, Server | Client/server callbacks with validation, timeouts and rate limits |
| `class` | Shared | Minimal class system with `constructor` and `new` |
| `colors` | Shared | ESX brand colors and UI theme from convars |
| `dui` | Client | DUI (browser texture) class |
| `entity` | Client | Closest entity and distance helpers, teleport |
| `game` | Client | Spawn and delete vehicles and objects, closest player, ped mugshots |
| `interactions` | Client | Shared "Interact" key (`E` by default) with conditional handlers |
| `isEnhanced` | Shared | Detects the GTA V Enhanced build |
| `markerZone` | Client | Marker plus interaction zone built on `points` |
| `math` | Shared | Clamp, rounding, conversion, lerp, color and heading helpers |
| `medal` | Client | Trigger a Medal.tv clip through the local Medal client |
| `nui` | Client | `SendNUIMessage`, focus and NUI callback helpers |
| `onesync` | Server | Players and entities in an area (server-side OneSync) |
| `overload` | Shared | Type-based function overloading |
| `player` | Server | Player ped, coords and distance checks |
| `point` / `points` | Client | Proximity points with `enter`, `leave` and `inside` |
| `pubsub` | Client, Server | Server-driven topic subscriptions |
| `rateLimiter` | Server | Token bucket rate limiter |
| `raycast` | Client | Raycasts from the camera |
| `require` | Shared | Module loader, `xLib.load` and `xLib.loadJson` |
| `scaleform` | Client | Freemode messages, breaking news, popup warnings |
| `schema` | Server | Database column inspection and safe migrations (oxmysql) |
| `server` | Server | Aliases for `player` distance checks |
| `streaming` | Client | Request models, dicts, assets with timeout |
| `string` | Shared | Case conversion, safe matching, `randomHex`, `uuid` |
| `table` | Shared | Array and table helpers |
| `timeout` | Shared | Cancellable `setTimeout` and `clearTimeout` |
| `token` | Server | Single-use, expiring tokens |
| `validation` | Shared, Server | Validate numbers, money, strings and player proximity |
| `vehiclePlate` | Shared | Normalize, compare and generate plates |
| `verify` | Shared | Runtime type checks |
| `waitFor` | Shared | Yield until a function returns a value |

The `catalog` folder is not a regular module. es_extended loads `imports/catalog/bootstrap.lua` directly (see [SQL catalog](#sql-catalog)).

## Callbacks [#callbacks]

`xLib.callback` is the callback system ESX uses internally. `ESX.TriggerServerCallback` and `ESX.RegisterServerCallback` are compat wrappers around it (`xLib.callback.registerCompat`). For new code, use xLib directly or the ESX wrappers described in [Callbacks](/docs/guides/callbacks).

### xLib.callback.register [#xlibcallbackregister]

```lua
xLib.callback.register(name, cb)
```

| Name | Type | Description |
| --- | --- | --- |
| `name` | `string` | Unique callback name (max 200 chars). A name owned by another resource raises an error. |
| `cb` | `function` | Server: `function(source, ...)`. Client: `function(...)`. Its return values are sent back. |

Errors inside `cb` are printed with a stack trace and the caller receives `false`.

### xLib.callback.await [#xlibcallbackawait]

```lua
-- client
local ... = xLib.callback.await(name, delay, ...)
-- server
local ... = xLib.callback.await(name, playerId, ...)
```

| Name | Type | Description |
| --- | --- | --- |
| `name` | `string` | Callback name registered on the other side |
| `delay` | `number` or `false` | Client only. Throttle in ms: further calls to the same name within this window return immediately without a request. |
| `playerId` | `number` | Server only. Target player |
| `...` | `any` | Arguments passed to the handler |

Yields the current thread and returns the handler's return values. Rejects with `callback '<name>' does not exist` if nothing is registered, or with a timeout error.

### xLib.callback (with function) [#xlibcallback-with-function]

```lua
xLib.callback(name, delayOrPlayerId, cb, ...)
```

Same as `await`, but calls `cb(...)` with the results instead of yielding. Omitting `cb` prints a warning and falls back to await.

<Tabs items={['Server', 'Client']}>
<Tab value="Server">

```lua title="server/main.lua"
xLib.callback.register('myshop:getBalance', function(source, account)
    local xPlayer = ESX.GetPlayerFromId(source)
    if not xPlayer then return false end

    return xPlayer.getAccount(account).money
end)
```

</Tab>
<Tab value="Client">

```lua title="client/main.lua"
CreateThread(function()
    -- throttle: at most one request per second
    local balance = xLib.callback.await('myshop:getBalance', 1000, 'bank')
    print(('Bank balance: %s'):format(balance))
end)
```

</Tab>
</Tabs>

### xLib.callback.registerCompat [#xlibcallbackregistercompat]

```lua
xLib.callback.registerCompat(name, cb, owner)
```

Registers a handler with the legacy ESX signature: server `function(source, cb, ...)`, client `function(cb, ...)`. `owner` is the resource whose stop removes the handler. es_extended uses this for `ESX.RegisterServerCallback` and `ESX.RegisterClientCallback`.

### Callback limits [#callback-limits]

Server callbacks are protected by per-player budgets. When a player exceeds them, the request is answered with `false`. All values are server convars.

| Convar | Default | Meaning |
| --- | --- | --- |
| `xLib:callbackTimeout` | falls back to `esx:callbackTimeout`, then 300000 ms | Await timeout. Replicated to clients when set. |
| `xLib:callbackBurst` | `60` | Request bucket capacity per player |
| `xLib:callbackRate` | `30` | Requests refilled per second |
| `xLib:callbackByteBurst` | `262144` | Payload byte bucket per player |
| `xLib:callbackByteRate` | `65536` | Bytes refilled per second |
| `xLib:callbackMaxPayloadBytes` | `65536` | Max packed size of one request |
| `xLib:callbackMaxInFlight` | `8` | Concurrent callbacks per player |
| `xLib:callbackMaxGlobalInFlight` | `4096` | Concurrent callbacks server-wide |
| `xLib:callbackValidationGrace` | `15000` | Delay before an unknown callback name is rejected (capped at half the timeout) |
| `xLib:callbackValidationWindow` | `5000` | Window for counting invalid-name validations |
| `xLib:callbackValidationMaxRequests` | `30` | Max invalid-name validations per window |

The server-side receiver also gives up after the configured timeout, or 30 seconds when none is set. Payloads are limited to 32 arguments, 2048 entries and 8 levels of nesting.

## Client cache [#client-cache]

```lua
local cache = xLib.cache
```

| Key | Type | Description |
| --- | --- | --- |
| `playerId` | `number` | `PlayerId()` |
| `serverId` | `number` | `GetPlayerServerId(PlayerId())`, resolved lazily |
| `ped` | `number` | Current player ped |
| `vehicle` | `number` or `false` | Current vehicle |
| `seat` | `number` or `false` | Seat index (`-1` is the driver) |
| `weapon` | `number` or `false` | Selected weapon hash, `false` when unarmed |
| `coords` | `vector3` | Read live on every access |

esx_lib polls every 100 ms and triggers a local event `xLib:cache:<key>` with `(value, previous)` when `ped`, `vehicle`, `seat` or `weapon` change. Other resources update their copy from that event.

```lua title="client/main.lua"
AddEventHandler('xLib:cache:vehicle', function(vehicle, previous)
    if vehicle then
        print('Entered vehicle', vehicle, 'seat', xLib.cache.seat)
    else
        print('Left vehicle', previous)
    end
end)
```

## Points and marker zones [#points-and-marker-zones]

### xLib.points.create [#xlibpointscreate]

```lua
local handle = xLib.points.create(coords, distance, hidden, enter, leave, inside)
```

| Name | Type | Description |
| --- | --- | --- |
| `coords` | `vector3` | Point position |
| `distance` | `number` | Radius that counts as "nearby" |
| `hidden` | `boolean?` | Hidden points never trigger |
| `enter` | `function` | Called once when the player enters the radius |
| `leave` | `function` | Called once when the player leaves |
| `inside` | `function?` | Called every frame while nearby, with the current distance |

Also available: `xLib.points.remove(handle)` and `xLib.points.hide(handle, hidden)`. Proximity is scanned every 500 ms; the loop only runs every frame while at least one point with `inside` is active. Points are removed automatically when the creating resource stops.

`xLib.point` is the class form: `xLib.point:new({ coords, distance, hidden, enter, leave, inside })` with methods `:delete()` and `:toggle(hidden?)`. Its callbacks receive `self`.

### xLib.markerZone.create [#xlibmarkerzonecreate]

```lua
local zone = xLib.markerZone.create(data)
```

| Field | Type | Description |
| --- | --- | --- |
| `coords` | `vector3` or table | Zone center |
| `drawDistance` | `number?` | Marker draw radius (alias `distance`), default `10.0` |
| `interactDistance` | `number?` | Interaction radius (aliases `actionDistance`, `enterDistance`), default `1.5` |
| `exitDistance` | `number?` | Radius to leave interaction, defaults to `interactDistance` |
| `marker` | table or `false` | Marker options, `false` disables drawing |
| `hidden` | `boolean?` | Start hidden |
| `canDraw` / `canInteract` | `function(distance)?` | Extra conditions |
| `onEnter` / `onInside` / `onExit` | `function(distance)?` | Interaction radius handlers |
| `onEnterPoint` / `onExitPoint` | `function?` | Draw radius handlers |

Returns `{ handle, remove(), hide(hidden?), show() }`, or `nil` when `coords` is invalid.

```lua title="client/main.lua"
local zone = xLib.markerZone.create({
    coords = vector3(25.7, -1347.3, 29.5),
    interactDistance = 1.5,
    onEnter = function()
        ESX.TextUI('[E] Open shop')
    end,
    onExit = function()
        ESX.HideUI()
    end,
})
```

## Interactions and keybinds [#interactions-and-keybinds]

### xLib.addKeybind [#xlibaddkeybind]

```lua
local keybind = xLib.addKeybind(data)
```

| Field | Type | Description |
| --- | --- | --- |
| `name` | `string` | Command name (registered as `+name` / `-name`) |
| `description` | `string` | Label in the FiveM key bindings menu |
| `defaultMapper` | `string?` | Input mapper, default `keyboard` |
| `defaultKey` | `string?` | Default key |
| `disabled` | `boolean?` | Start disabled |
| `onPressed` / `onReleased` | `function(self)?` | Handlers, skipped while the pause menu is open |

The returned object has `currentKey`, `isPressed`, `getCurrentKey()`, `isControlPressed()`, `disable(toggle)` and `remove()`.

### xLib.interactions [#xlibinteractions]

esx_lib registers one keybind named `esx_interact` (default `E`) and triggers the local event `xLib:interact` when it is pressed. `xLib.interactions` lets many resources share that key:

```lua
xLib.interactions.register(name, onPress, condition)
xLib.interactions.remove(name)
local key = xLib.interactions.getInteractKey() -- e.g. "E"
```

Every registered interaction whose `condition()` returns true runs `onPress()`. Interactions are removed when their resource stops.

## NUI helpers [#nui-helpers]

| Function | Description |
| --- | --- |
| `xLib.nui.send(message, data?)` | Sends a table as-is, or `{ action = message, data = data }` when `message` is a string |
| `xLib.nui.focus(hasFocus, hasCursor?, keepInput?)` | `SetNuiFocus`; cursor defaults to `hasFocus` |
| `xLib.nui.open(message?, focus?, cursor?, keepInput?)` | Send and focus (skip focus with `focus = false`) |
| `xLib.nui.close(message?)` | Send and drop focus |
| `xLib.nui.ok(data?)` / `xLib.nui.fail(error, code?)` | Build `{ ok, data }` / `{ ok = false, error, code }` responses |
| `xLib.nui.register(name, handler)` | `RegisterNUICallback` wrapper with error handling |

`handler(data, reply)` can return a value (sent as response, `nil` becomes `ok()`), or call `reply(response)` later. Handlers that declare two parameters and return `nil` are treated as deferred; they time out after `xLib:nuiDeferTimeout` ms (default `10000`) and reply `fail('timeout', 'timeout')`.

## Streaming [#streaming]

All functions accept an optional `cb`. Without it they yield and return the loaded asset; with it they call `cb(asset)`. Requests time out after `xLib:streamingTimeout` ms (default `10000`).

| Function | Returns |
| --- | --- |
| `xLib.streaming.requestModel(modelHash, cb?)` | Model hash or `nil` |
| `xLib.streaming.requestModelWithSpinner(modelHash, message?, cb?)` | Model hash or `nil` |
| `xLib.streaming.requestStreamedTextureDict(textureDict, cb?)` | Dict name |
| `xLib.streaming.requestNamedPtfxAsset(assetName, cb?)` | Asset name |
| `xLib.streaming.requestAnimSet(animSet, cb?)` | Anim set |
| `xLib.streaming.requestAnimDict(animDict, cb?)` | Anim dict |
| `xLib.streaming.requestWeaponAsset(weaponHash, cb?)` | Weapon hash |
| `xLib.streaming.requestAudioBank(bankName, cb?)` | Bank name |

## Game and entity helpers (client) [#game-and-entity-helpers-client]

| Function | Description |
| --- | --- |
| `xLib.game.spawnVehicle(model, coords, heading, cb?, networked?)` | Spawns a vehicle (networked by default). Yields and returns the handle when `cb` is omitted. Errors if `coords` is more than 424 units from the player. |
| `xLib.game.spawnLocalVehicle(model, coords, heading, cb?)` | Non-networked vehicle |
| `xLib.game.spawnObject(model, coords, cb?, networked?)` | Spawns an object |
| `xLib.game.spawnLocalObject(model, coords, cb?)` | Non-networked object |
| `xLib.game.deleteVehicle(vehicle)` / `deleteObject(object)` | Deletes the entity |
| `xLib.game.isVehicleEmpty(vehicle)` | `true` when no ped is inside |
| `xLib.game.getPeds(onlyOtherPeds?)` / `getVehicles()` / `getObjects()` | Entity pools |
| `xLib.game.getPlayers(onlyOtherPlayers?, returnKeyValue?, returnPeds?)` | Active players |
| `xLib.game.getClosestPlayer(coords?)` / `getClosestPed(coords?, modelFilter?)` / `getClosestObject(coords?, modelFilter?)` / `getClosestVehicle(coords?, modelFilter?)` | Returns `entity, distance` |
| `xLib.game.getPlayersInArea(coords, maxDistance)` / `getVehiclesInArea(coords, maxDistance)` | Entities in a radius |
| `xLib.game.isSpawnPointClear(coords, maxDistance)` | `true` when no vehicle is in the radius |
| `xLib.game.getVehicleInDirection()` | Vehicle in front of the player |
| `xLib.game.getVehicleProperties(vehicle)` / `setVehicleProperties(vehicle, props)` | Read and apply vehicle mods, colors, extras |
| `xLib.game.getPedMugshot(ped, transparent?)` | Returns headshot handle and txd string |
| `xLib.entity.closest(entities, isPlayerEntities, coords?, modelFilter?)` | Closest entity and distance |
| `xLib.entity.EnumerateWithinDistance(entities, isPlayerEntities, coords?, maxDistance)` | Entities within distance |
| `xLib.entity.Teleport(entity, coords, cb?)` | Teleports and waits for collision |

The `ESX.Game.*` client functions in es_extended are direct aliases of these (`ESX.Game.SpawnVehicle = xLib.game.spawnVehicle` and so on). See [ESX.Game](/docs/es_extended/client/modules/game).

## Server player and OneSync helpers [#server-player-and-onesync-helpers]

`source` may be a number or numeric string. Functions return `nil` (or `false`) for invalid or disconnected players instead of erroring.

| Function | Returns |
| --- | --- |
| `xLib.player.getPed(source)` | Ped or `nil` |
| `xLib.player.getCoords(source)` | `vector3` or `nil` |
| `xLib.player.distanceToCoords(source, coords)` | Distance or `nil` |
| `xLib.player.isNearCoords(source, coords, distance)` | `nearby, actualDistance` |
| `xLib.player.distanceToPlayer(source, target)` | Distance or `nil` |
| `xLib.player.isNearPlayer(source, target, distance)` | `nearby, actualDistance` |
| `xLib.player.findNearbyCoords(source, coordsList, distance)` | `coords, index, actualDistance` |
| `xLib.player.isNearAnyCoords(source, coordsList, distance)` | `nearby, index, actualDistance` |
| `xLib.onesync.getPlayersInArea(source, maxDistance, ignore?, routingBucket?)` | Players near a player id or `vector3` |
| `xLib.onesync.getClosestPlayer(source, maxDistance, ignore?, routingBucket?)` | Closest player |
| `xLib.onesync.getPedsInArea` / `getObjectsInArea` / `getVehiclesInArea(coords, maxDistance, modelFilter?)` | Entities in area |
| `xLib.onesync.getClosestPed` / `getClosestObject` / `getClosestVehicle(coords, modelFilter?)` | `entity, distance, coords` |

`xLib.server.*` (`getPlayerCoords`, `distanceToCoords`, `isPlayerNearCoords`, `distanceToPlayer`, `isPlayerNearPlayer`, `isPlayerNearAnyCoords`) and `xLib.validation.playerNearCoords` / `playerNearPlayer` are aliases of the same checks. Use them to validate client requests, for example that a player is actually standing at a shop before you charge them:

```lua title="server/main.lua"
local SHOP <const> = vector3(25.7, -1347.3, 29.5)

xLib.callback.register('myshop:buy', function(source, item)
    if not xLib.player.isNearCoords(source, SHOP, 5.0) then
        return false
    end

    -- process purchase
    return true
end)
```

## Validation and type checks [#validation-and-type-checks]

### xLib.validation (shared) [#xlibvalidation-shared]

| Function | Returns |
| --- | --- |
| `xLib.validation.number(value, min?, max?, round?)` | Finite number in range, else `nil` |
| `xLib.validation.integer(value, min?, max?, mode?)` | Integer (`mode`: `round`, `floor`, `ceil`), else `nil` |
| `xLib.validation.count(value, max?)` | Positive item count, else `nil` |
| `xLib.validation.money(value, min?, max?)` | Rounded non-negative amount, else `nil` |
| `xLib.validation.string(value, options?)` | Trimmed string, else `nil`. Options: `trim`, `allowEmpty`, `minLength`, `maxLength`, `pattern` |
| `xLib.validation.oneOf(value, values)` | `true` when `value` is in the array |
| `xLib.validation.player(source)` | Server only. `true` for a connected player |

```lua title="server/main.lua"
RegisterNetEvent('myshop:deposit', function(amount)
    local value = xLib.validation.money(amount, 1, 1000000)
    if not value then return end

    local xPlayer = ESX.GetPlayerFromId(source)
    if xPlayer and xPlayer.getMoney() >= value then
        xPlayer.removeMoney(value, 'Shop deposit')
        xPlayer.addAccountMoney('bank', value, 'Shop deposit')
    end
end)
```

### xLib.verify [#xlibverify]

```lua
local ok = xLib.verify(value, types, throwError)
```

Checks `value` against one type or an array of types and returns a boolean, or raises an error when `throwError` is `true`. Supported types: Lua types plus `array`, `int`, `uint`, `float`, `char`, `vector3`, `vector4`, `ped`, `vehicle`, `prop`, `playerId`, `class` and `model`. On the server, `model` asks a random connected client to validate the model through the `xLib:validateModel` callback. `xLib.verify.debug(...)` only runs when the `xLib:debug` convar is `true`.

## Rate limiter and tokens (server) [#rate-limiter-and-tokens-server]

### xLib.rateLimiter [#xlibratelimiter]

```lua
local limiter = xLib.rateLimiter(options)
```

| Option | Type | Description |
| --- | --- | --- |
| `capacity` | `number` | Max tokens per key |
| `refill` | `number` | Tokens restored per interval |
| `interval` | `number` | Interval in ms |
| `maxEntries` | `number?` | Max tracked keys (convar `xLib:rateLimiterMaxEntries`, default 4096) |
| `staleMs` | `number?` | Idle time before a full bucket is pruned |
| `pruneInterval` | `number?` | Min time between prunes |

Methods: `limiter:consume(key, cost?)` returns `allowed, retryAfterMs`; `limiter:retryAfter(key, cost?)`, `limiter:reset(key)`, `limiter:clear()`, `limiter:size()`. Buckets keyed by a player id are reset on `playerDropped`.

```lua title="server/main.lua"
local limiter = xLib.rateLimiter({ capacity = 5, refill = 1, interval = 2000 })

RegisterNetEvent('myjob:search', function()
    local allowed, retryAfter = limiter:consume(source)
    if not allowed then
        return TriggerClientEvent('esx:showNotification', source, ('Wait %d s'):format(math.ceil(retryAfter / 1000)), 'error')
    end
    -- handle search
end)
```

### xLib.token [#xlibtoken]

| Function | Description |
| --- | --- |
| `xLib.token.create({ source?, ttl, data? })` | Returns a token id. `ttl` in ms (default 30000). Max entries via `xLib:tokenMaxEntries` (default 4096). |
| `xLib.token.consume(id, source?)` | Returns `data` once; the token is deleted. Fails if `source` does not match. |
| `xLib.token.remove(id)` | Deletes a token |
| `xLib.token.reset(source)` | Deletes all tokens of a player, returns count |
| `xLib.token.clear()` / `xLib.token.size()` | Clear all / count |

Tokens of a player are removed on `playerDropped`.

## Pub/sub [#pubsub]

The server subscribes players to topics and publishes data only to them. Clients can only listen. Topic names must match `^[%w_:%.%-]+$` (max 200 chars); prefix them with your resource name.

| Function | Side | Description |
| --- | --- | --- |
| `xLib.pubsub.subscribe(src, topic)` | Server | Returns `true` if added |
| `xLib.pubsub.unsubscribe(src, topic)` | Server | Returns `true` if removed |
| `xLib.pubsub.publish(topic, data)` | Server | Sends to all subscribers, returns the count |
| `xLib.pubsub.subscribers(topic)` | Server | Array of server ids |
| `xLib.pubsub.isSubscribed(src, topic)` | Server | Boolean |
| `xLib.pubsub.clear(topic)` | Server | Drops the topic, returns removed count |
| `xLib.pubsub.on(topic, handler)` | Client | `handler(data, topic)` |
| `xLib.pubsub.off(topic, handler?)` | Client | Removes one or all handlers |

Players are removed from all topics on disconnect, and topics are cleared when the resource that created them stops.

<Tabs items={['Server', 'Client']}>
<Tab value="Server">

```lua title="server/main.lua"
local TOPIC <const> = 'myshop:247:stock'

RegisterNetEvent('myshop:openShop', function()
    xLib.pubsub.subscribe(source, TOPIC)
end)

local function updateStock(stock)
    xLib.pubsub.publish(TOPIC, stock)
end
```

</Tab>
<Tab value="Client">

```lua title="client/main.lua"
xLib.pubsub.on('myshop:247:stock', function(stock)
    print(('Stock updated: %d items'):format(#stock))
end)
```

</Tab>
</Tabs>

## Shared utilities [#shared-utilities]

### Table, string and math [#table-string-and-math]

`xLib.table`: `isArray`, `searchForKey`, `filter`, `deepcopy`, `merge`, `dump`, `sizeOf`, `set`, `indexOf`, `lastIndexOf`, `find`, `findIndex`, `map`, `reverse`, `clone`, `concat`, `join`, `contains`, `sort` (sorted `pairs` iterator), `toArray`, `wipe`. The `ESX.Table.*` functions are direct aliases, see [ESX.Table](/docs/es_extended/shared/table).

`xLib.string`: `normalize`, `capitalize`, `toSnake`, `toCamel`, `toPascal`, `escapePattern`, `matchSafe`, `before`, `after`, `contains`, `replace`, `randomHex(length)`, `uuid()`.

`xLib.math`: `clamp`, `toNumber`, `tryNumber`, `toInteger`, `toScalars`, `toVector`, `normalToRotation`, `toRGBA`, `hexToRGBA`, `toHex`, `groupDigits`, `round`, `interpolate`, `lerp`, `inverseLerp`, `remap`, plus the ESX-style `Round`, `GroupDigits`, `Trim`, `Random`, `GetHeadingFromCoords` used by [ESX.Math](/docs/es_extended/shared/math).

### Timeout and waitFor [#timeout-and-waitfor]

```lua
local id = xLib.timeout.setTimeout(msec, cb)
xLib.timeout.clearTimeout(id)

local value = xLib.waitFor(cb, errMessage, timeout, interval)
```

`waitFor` calls `cb` until it returns a non-nil value. It errors after `timeout` ms (default `5000`; pass `false` to wait forever) and polls every `interval` ms (default `0`). `ESX.SetTimeout`, `ESX.ClearTimeout` and `ESX.Await` are aliases. See [Timeout](/docs/es_extended/shared/timeout).

```lua title="client/main.lua"
local vehicle = xLib.waitFor(function()
    local veh = GetVehiclePedIsIn(PlayerPedId(), false)
    if veh ~= 0 then return veh end
end, 'Player never entered a vehicle', 10000)
```

### Vehicle plates [#vehicle-plates]

| Function | Description |
| --- | --- |
| `xLib.vehiclePlate.normalize(plate, options?)` | Trimmed, normalized plate or `nil`. Options: `maxLength`, `uppercase`, `compact`, `allowEmpty`, `pattern`, `coerce` |
| `xLib.vehiclePlate.equals(left, right, options?)` | Compares two plates after normalizing |
| `xLib.vehiclePlate.matchesVehicle(vehicle, plate, options?)` | Compares against a vehicle's plate |
| `xLib.vehiclePlate.generate(options?)` | Random plate. Options: `prefix`, `suffix`, `letters` (3), `numbers` (3), `useSpace` |
| `xLib.vehiclePlate.generateUnique(options?, exists?)` | Retries up to `options.attempts` (30) until `exists(plate)` returns false; reserves the plate for 30 s |
| `xLib.vehiclePlate.randomLetters(length)` / `randomNumbers(length)` | Random chunks |

### Other shared modules [#other-shared-modules]

- `xLib.class(copy?)` creates a class; instances are made with `Class:new(...)`, which calls `constructor`.
- `xLib.overload(name, validTypes, cb, obj?)` registers a typed overload, dispatched by argument types.
- `xLib.isEnhanced()` returns `true` on the GTA V Enhanced build (server: `gamename` convar `gta5enhanced` or `gta5_enhanced`).
- `xLib.colors` exposes `brand`, `darkest`, `dark`, `mid`, `light`, `lightest` and `getESXTheme(defaults?)`. See [convars](#convars).

### require, load and loadJson [#require-load-and-loadjson]

```lua
local config = require 'config.shops'        -- loads config/shops.lua from your resource
local other  = require '@other_resource.data' -- loads data.lua from another resource
local result = xLib.load('scripts/once')     -- runs a file without caching
local data   = xLib.loadJson('data/items')   -- decodes data/items.json
```

Dots become slashes. Search paths are `./?.lua` and `./?/init.lua`. The file must be listed in the target resource's `files` or scripts so `LoadResourceFile` can read it on the client. Circular requires raise an error.

## Other client modules [#other-client-modules]

- `xLib.blips.create(data)`, `xLib.blips.createMany(items, defaults?)`, `xLib.blips.remove(blip)`. `data` can be a `vector3` or a table with `coords` or `entity` or `radius`, plus `sprite`, `display`, `scale`, `color`, `alpha`, `shortRange`, `label`, `route`, `routeColor` and more.
- `xLib.dui:new({ url, width, height, debug? })` with `remove()`, `setUrl(url)`, `sendMessage(message)`, `sendMouseMove(x, y)`, `sendMouseDown(button)`, `sendMouseUp(button)`, `sendMouseWheel(deltaX, deltaY)`. DUIs are destroyed when the resource stops.
- `xLib.scaleform.showFreemodeMessage(title, msg, sec)`, `showBreakingNews(title, msg, bottom, sec)`, `showPopupWarning(title, msg, bottom, sec)`, `showTrafficMovie(sec)`, plus `xLib.scaleform.utils.requestScaleformMovie(movie)` and `runMethod(scaleform, methodName, returnValue?, ...)`.
- `xLib.Raycast.FromScreen(depth, ...)`, `xLib.Raycast.Start(depth, ...)`, `xLib.Raycast:Stop()`, `xLib.Raycast:IsActive()`, `xLib.Raycast.GetShapeTestResult(shape)`.
- `xLib.medal.triggerClip(publicKey?, eventName?, clipOptions?)` and `xLib.medal.getConfig()`.

## Server-only modules [#server-only-modules]

- `xLib.schema` (needs oxmysql): `column(tableName, columnName)`, `identifier(value)`, `ensureColumn(tableName, columnName, definition)`, `widenVarchar(tableName, columnName, length, column?)`, `ensureProperty()`, `ensureMileage()`. es_extended migrations and esx_multicharacter use it to adjust tables safely.
- `xLib.triggerClientEvent(eventName, targets, ...)`: `targets` is one server id or an array. For arrays, the payload is packed once and sent to each client, which is cheaper than calling `TriggerClientEvent` in a loop.

### SQL catalog [#sql-catalog]

`imports/catalog/bootstrap.lua` is loaded as a server script by es_extended, esx_identity, esx_multicharacter and esx_skin. On start it reads `imports/catalog/data.json` (catalog version `1.16.0`) and creates missing tables, columns and seed rows (jobs, items, licenses, vehicles and more) for ESX core and official addons, using the locale from `Config.SqlCatalogLocale`, `Config.Locale` or the `esx:locale` convar. Other resources call the es_extended export `EnsureSqlCatalog(name, locale)`, which only lets a resource prepare its own catalog. When done, it triggers the server event `esx:sqlCatalogReady` with the list of touched catalogs. If it fails, resource initialization is blocked and the console prints `SQL catalog FAILED`.

## Exports [#exports]

Functions defined in esx_lib's own `resource/` scripts are exported automatically. You normally reach them through xLib, but they are callable as `exports.esx_lib:<name>(...)`:

| Export | Side | Description |
| --- | --- | --- |
| `setValidCallback(name, isValid, owner?)` | Both | Internal callback ownership registry |
| `isCallbackValid(name)` | Both | `true` if a callback name is registered |
| `acquireCallbackBudget` / `releaseCallbackBudget` / `chargeCallbackBytes` | Server | Internal callback rate budget |
| `pubsub_subscribe`, `pubsub_unsubscribe`, `pubsub_publish`, `pubsub_subscribers`, `pubsub_isSubscribed`, `pubsub_clear` | Server | Pub/sub registry |
| `triggerClientEvent(eventName, targets, ...)` | Server | Packed multi-target event |
| `getMedalConfig()` / `triggerMedalClip(publicKey, eventName, clipOptions)` | Client | Medal.tv integration |

## Events [#events]

| Event | Side | Type | Payload |
| --- | --- | --- | --- |
| `xLib:cache:<key>` | Client | Local | `value, previous` for `ped`, `vehicle`, `seat`, `weapon` |
| `xLib:interact` | Client | Local | Fired when the `esx_interact` key is pressed |
| `xLib:validateCallback` | Both | Net (internal) | Callback validation |
| `esx:sqlCatalogReady` | Server | Local | `catalogs` table, fired by the catalog bootstrap |

## Configuration [#configuration]

`config.lua` only configures the Medal.tv integration. esx_lib's NUI page posts clip requests to the Medal desktop client at `http://localhost:12665`, so nothing happens for players who do not run Medal.

| Key | Default | Description |
| --- | --- | --- |
| `Config.Medal.enabled` | `true` | Allow `xLib.medal.triggerClip` |
| `Config.Medal.publicKey` | ESX public key | Medal API public key |
| `Config.Medal.eventName` | `'Death'` | Default event name |
| `Config.Medal.clipOptions.duration` | `30` | Clip length in seconds |
| `Config.Medal.clipOptions.captureDelayMs` | `0` | Delay before capture |
| `Config.Medal.clipOptions.alertType` | `'Default'` | `Default`, `Disabled`, `SoundOnly` or `OverlayOnly` |

### Convars [#convars]

| Convar | Default | Description |
| --- | --- | --- |
| `xLib:debug` | `false` | Debug output, enables `xLib.verify.debug` |
| `xLib:nuiDeferTimeout` | `10000` | Deferred NUI callback timeout (ms) |
| `xLib:streamingTimeout` | `10000` | Streaming request timeout (ms) |
| `xLib:rateLimiterMaxEntries` | `4096` | Default max keys per rate limiter |
| `xLib:tokenMaxEntries` | `4096` | Max active tokens |
| `esx:brand-color` | `#FB9B04` | `xLib.colors.brand` |
| `esx:darkest-color` / `esx:dark-color` / `esx:mid-color` / `esx:light-color` / `esx:lightest-color` | `#161616` / `#252525` / `#383838` / `#969696` / `#F2F2F2` | Palette |
| `esx:ui:primaryColor`, `esx:ui:secondaryColor`, `esx:ui:backgroundColor`, `esx:ui:accentColor`, `esx:ui:logoUrl` | palette values | Theme returned by `xLib.colors.getESXTheme()` |

Callback convars are listed in [Callback limits](#callback-limits).

```bash title="server.cfg"
setr esx:ui:primaryColor "#3B82F6"
set xLib:callbackTimeout 60000
```

## Version reporting [#version-reporting]

On the server, esx_lib publishes the `version` and `legacyversion` metadata of every ESX resource (names starting with `esx_` or `es_`, or that depend on es_extended or esx_lib) as server info convars named `<resource>-version`. This is what server lists and support tools read to see which ESX version you run. It runs 2 seconds after start and again for each ESX resource that starts later.

## Common issues [#common-issues]

- **`attempt to index a nil value (global 'xLib')`**: `@esx_lib/imports.lua` is missing from `shared_scripts`, or esx_lib is not started.
- **Renamed folder**: the loader reads files from a resource literally named `esx_lib`. Keep the name.
- **`callback '<name>' does not exist`**: the name is not registered on the receiving side, or its resource stopped. On the server this is reported after the validation grace period (up to 15 s).
- **`callback '<name>' is already owned by resource '<x>'`**: two resources registered the same callback name. Prefix names with your resource.
- **Callback returns `false` under load**: the player hit a callback budget. Check the [callback limits](#callback-limits) and avoid calling server callbacks every frame; use the client `delay` argument.
- **`require` cannot find a file on the client**: the file is not in the resource's `files` list, so `LoadResourceFile` cannot read it.
- **`SQL catalog FAILED`**: oxmysql is not connected or the database user cannot create or alter tables.

## Related [#related]

- [ESX Legacy core resources](/docs/core)
- [Callbacks guide](/docs/guides/callbacks)
- [ESX shared functions](/docs/es_extended/shared)
- [Client callback module](/docs/es_extended/client/modules/callback)
- [OneSync server functions](/docs/es_extended/server/onesync)
