Lua Annotations for ESX
Use LuaLS annotations in ESX scripts - document functions, classes and types, reuse the xPlayer and ESXJob types from es_extended, and set up VS Code for autocomplete.
Annotations are special comments that tell the Lua language server (LuaLS, the "Lua" extension by sumneko) what types your functions take and return. Your editor then shows autocomplete, parameter hints and warnings, for example when you pass a string where a number is expected or forget to check a value that can be nil. They cost nothing at runtime: FiveM treats them as ordinary comments.
ESX Legacy itself is annotated, so with the right setup you get full autocomplete for xPlayer, accounts, jobs and items in your own scripts.
Editor setup
- Install Lua by sumneko and CfxLua IntelliSense.
- Point the language server at your copy of
es_extendedso it reads the ESX types:
{
"Lua.runtime.version": "Lua 5.4",
"Lua.workspace.library": [
"C:/FXServer/esx-server/resources/[core]/es_extended"
],
"Lua.diagnostics.globals": ["ESX", "Config"]
}Use the real path to your server's es_extended folder. On Linux or macOS use a normal Unix path.
Annotation basics
An annotation is a comment line that starts with --- followed by a tag such as @param. It applies to the code directly below it.
| Tag | Purpose | Example |
|---|---|---|
@param | Type of a function parameter. Add ? for optional. | ---@param amount number |
@return | Type of a return value, optionally with a name. | ---@return boolean success |
@class | Declares a table type. Can extend another class. | ---@class ShopItem |
@field | A field of a class. | ---@field price number |
@type | Type of a variable. | ---@type xPlayer? |
@alias | A name for a type or a set of literal values. | ---@alias Account 'money'|'bank' |
@cast | Tell the server a variable has a narrower type from here on. | ---@cast xPlayer xPlayer |
@deprecated | Marks a function as deprecated; callers get a strikethrough. | ---@deprecated |
@diagnostic | Disable a warning for a line or file. | ---@diagnostic disable-next-line: undefined-global |
Common types: string, number, integer, boolean, table, function, nil, any, arrays like string[], maps like table<string, number>, unions like string|number, and FiveM's vector3.
Documenting a function
--- Pays a player from a society account.
--- Returns false when the player is not loaded or the amount is invalid.
---@param playerId number Server ID of the player
---@param amount integer Amount in dollars, must be positive
---@param reason? string Shown in logs, defaults to "Payment"
---@return boolean success
local function payPlayer(playerId, amount, reason)
if amount <= 0 then return false end
local xPlayer = ESX.GetPlayerFromId(playerId)
if not xPlayer then return false end
xPlayer.addAccountMoney('bank', amount, reason or 'Payment')
return true
endNow hovering payPlayer shows the description, and calling payPlayer('1', 500) gets a warning because '1' is a string.
Using the ESX types
es_extended defines these classes in server/classes/player.lua:
| Class | Describes |
|---|---|
xPlayer | The full server-side player object (extends StaticPlayer) |
StaticPlayer | The method interface of a player |
ESXAccount | An account: name, money, label, round, index |
ESXJob | A job: name, label, grade, grade_name, grade_label, grade_salary, onDuty, ... |
ESXItem, ESXInventoryItem | An item and an item with count |
ESXInventoryWeapon | A weapon in the loadout with ammo, components, tintIndex |
Use them in your own annotations:
---@param xPlayer xPlayer
---@return boolean
local function isOnDutyPolice(xPlayer)
local job = xPlayer.job ---@type ESXJob
return job.name == 'police' and job.onDuty == true
end
RegisterNetEvent('my_police:action', function()
local xPlayer = ESX.GetPlayerFromId(source) ---@type xPlayer?
if not xPlayer then return end
if isOnDutyPolice(xPlayer) then
-- autocomplete works here: xPlayer.getAccount, xPlayer.showNotification, ...
end
end)Marking the variable as xPlayer? (nullable) makes the language server warn you if you use it before the nil check, which is exactly the bug that causes most attempt to index a nil value errors.
Declaring your own classes
Describe the shape of your config or data tables once and get checks everywhere you use them.
---@class ShopItem
---@field name string Item name from the items table
---@field label string
---@field price integer
---@class ShopConfig
---@field coords vector3
---@field items ShopItem[]
---@type ShopConfig
Config = {
coords = vector3(25.7, -1347.3, 29.5),
items = {
{ name = 'water', label = 'Water', price = 25 },
{ name = 'bread', label = 'Bread', price = 15 },
},
}A typo like prcie = 15 now shows a warning instead of a silent nil at runtime.
Aliases for fixed values
---@alias NotifyType 'info'|'success'|'error'
---@param message string
---@param notifyType? NotifyType
local function notify(message, notifyType)
ESX.ShowNotification(message, notifyType or 'info')
endThe editor now suggests only the three valid notification types.
Tips
- Annotate public functions and config tables first. That is where most type mistakes happen.
- Keep descriptions short. One line about what and anything surprising about when it returns
nil. - Do not disable diagnostics for a whole workspace to silence one warning. Use
---@diagnostic disable-next-linewhere needed. - The full annotation reference is in the LuaLS wiki.
Related
Frequently asked questions
Independent documentation
Last updated on
Coding Practices for ESX
Best practices for ESX Legacy scripts - naming, locals vs globals, efficient loops, event-driven code, caching, modern natives, Lua 5.4 features and server security.
Updating ESX Legacy
How to update ESX Legacy safely - back up database and resources, replace the core, let migrations run, apply optional SQL hardening and test before going live.