Volume 2 · Free handbook
The FiveM Developer Handbook
Everything a FiveM developer needs in one place: the language, how resources are put together, how the client and server talk, the natives and threads that drive the game, where to keep state, how to use the database and the popular frameworks, how to build NUI, how to write code that cannot be exploited and does not cost frames, how to debug it, and how to ship and sell it. Every chapter has working code and links to a detailed guide.
- chapters
- 20
- reading time
- 65 min
- APIs & natives
- 33
- glossary terms
- 34
Who this handbook is for
- Beginners writing their first FiveM resource
- Server owners who edit scripts and want to understand them
- Developers moving between ESX, QBCore, Qbox and ox_core
- Script sellers who want secure, fast, well-documented products
Updated 11 September 2026 · checked against the official Cfx.re docs
Your first resource in ten minutes
- 01Install VS Code with the Lua Language Server extension and a local FXServer — see local development server.
- 02Create
resources/[local]/hello/with anfxmanifest.lua:fx_version 'cerulean',game 'gta5',client_script 'client.lua',server_script 'server.lua'. - 03In
server.lua:RegisterCommand('hello', function(src) print(('hello from %s'):format(GetPlayerName(src))) end). - 04In
client.lua:RegisterCommand('pos', function() print(GetEntityCoords(PlayerPedId())) end). - 05Run
refreshandensure helloin the server console, connect withconnect localhost:30120. - 06Type
/helloand/posin chat; read the output in the server console and in F8.
Chapter 01
Languages, runtimes and tooling
FiveM runs Lua, JavaScript/TypeScript and C#. Almost every public resource and every major framework is Lua, so start there and set up an editor that understands it.
| Runtime | Use it for | Notes |
|---|---|---|
| Lua (CfxLua, based on Lua 5.4) | Almost everything | Vectors, backtick hashes, json, promise and msgpack built in |
| JavaScript / TypeScript | NUI, npm libraries on the server | Server JS runs on Node.js 16 by default, or Node 22 with node_version '22' |
| C# (.NET) | Teams already on .NET | dotnet new cfx-resource templates; strong typing |
- Editor: VS Code with the Lua Language Server (LuaLS) plus Overextended’s
fivem-lls-addonfor native and CfxLua typings. - Local server: FXServer on your PC with a separate development database;
sv_lan trueskips the key check. - Version control: Git from day one, with secrets in an ignored
secrets.cfg. - Typings for JS/TS:
@citizenfx/clientand@citizenfx/server.
{
"runtime.version": "Lua 5.4",
"workspace.userThirdParty": ["C:/lua-addons"],
"workspace.library": ["../[libs]/ox_lib"],
"diagnostics.globals": ["lib", "cache"]
}Chapter 02
Lua for FiveM in one chapter
You do not need all of Lua to write good FiveM scripts. You need locals, tables, loops, functions and a handful of CfxLua extras.
local price = 250 -- always local
local name = 'Adder'
local owned = false
-- only nil and false are falsy: 0 and '' are true
if price > 100 and not owned then
print(('%s costs $%d'):format(name, price))
end
-- arrays start at 1
local garages = { 'pillbox', 'legion', 'airport' }
for i = 1, #garages do print(i, garages[i]) end
-- dictionaries
local prices = { adder = 1000000, sultan = 45000 }
for model, p in pairs(prices) do print(model, p) end
-- functions are values
local function tax(amount) return math.floor(amount * 0.15) endlocal pos = vector3(215.8, -810.1, 30.7) -- a real vector type
local here = GetEntityCoords(PlayerPedId())
local dist = #(here - pos) -- distance, fast
if GetEntityModel(veh) == `adder` then end -- compile-time hash
local data = json.decode('{"a":1}') -- json is globalChapter 03
Anatomy of a resource
A resource is a folder in resources/ with an fxmanifest.lua. The manifest decides which files run where, which files are sent to players and what must start first.
nc_garage/
fxmanifest.lua
config.lua shared settings
server/config.lua server-only settings (secrets, rewards)
client/main.lua
server/main.lua
locales/en.json
web/dist/ built NUI
sql/install.sql
README.mdfx_version 'cerulean'
game 'gta5'
lua54 'yes'
name 'nc_garage'
author 'you'
version '1.0.0'
shared_scripts { '@ox_lib/init.lua', 'config.lua' }
client_scripts { 'client/*.lua' }
server_scripts { '@oxmysql/lib/MySQL.lua', 'server/config.lua', 'server/*.lua' }
ui_page 'web/dist/index.html'
files { 'web/dist/**', 'locales/*.json' }
dependencies { 'ox_lib', 'oxmysql' }
escrow_ignore { 'config.lua', 'server/config.lua', 'locales/*.json' }| Entry | Meaning |
|---|---|
client_scripts | Run on every player’s PC — and are downloaded by them |
server_scripts | Run only on the server; never sent to players |
shared_scripts | Run on both sides |
files | Extra files clients may load (NUI, JSON, meta) |
ui_page | The HTML page for this resource’s NUI |
dependencies | Resources that must be running first |
escrow_ignore | Files that stay readable when the resource is escrowed |
Chapter 04
Client vs server: who does what
The single most important rule in FiveM development: the client asks, the server decides.
| Client scripts | Server scripts |
|---|---|
| Drawing markers, text, UI | Money, items, jobs, permissions |
| Reading keys and controls | Database reads and writes |
| Animations, camera, local effects | Validating every request from clients |
| Zones, targets, proximity checks | Server-side entity creation |
| Anything a cheater could change | Secrets: tokens, webhooks, API keys |
A client script can be read and modified by the player who runs it, and any event it sends can be sent by a cheat menu with different values. So the client only ever sends a request (“I want to buy item X at shop Y”), and the server looks up the price, checks distance, money and cooldowns, and makes the change. Full explanation: client vs server scripts.
Chapter 05
Events, callbacks and exports
Resources and the two sides of a resource talk through events (fire and forget), callbacks (request and answer) and exports (direct function calls between resources).
-- server: allow the event from the network and handle it
RegisterNetEvent('garage:store', function(netId)
local src = source
-- validate, then act
end)
-- client -> server
TriggerServerEvent('garage:store', NetworkGetNetworkIdFromEntity(veh))
-- server -> one client, or everyone with -1
TriggerClientEvent('garage:notify', src, 'Stored')
TriggerClientEvent('garage:notify', -1, 'Garage open')-- server
lib.callback.register('garage:list', function(source)
return MySQL.query.await('SELECT plate, model FROM vehicles WHERE owner = ?', { GetOwner(source) })
end)
-- client
local cars = lib.callback.await('garage:list', false)-- in nc_garage
exports('IsVehicleStored', function(plate) return Stored[plate] == true end)
-- in any other resource
local stored = exports.nc_garage:IsVehicleStored('ABC 123')| Use | When |
|---|---|
TriggerServerEvent / TriggerClientEvent | One side tells the other something happened |
Callback (lib.callback, ESX/QB callbacks) | One side needs an answer from the other |
exports | Another resource on the same side needs your function |
AddEventHandler without RegisterNetEvent | Events only other scripts on the same side may trigger |
Chapter 06
Natives, threads and Wait
Natives are the game’s functions; threads are how your code runs alongside the game without freezing it.
The natives reference lists every native by namespace. In Lua and JS they are called in PascalCase: GET_ENTITY_COORDS becomes GetEntityCoords. Game natives are client-side; the CFX namespace adds server natives such as GetPlayerIdentifiers, SetPlayerRoutingBucket and CreateVehicleServerSetter. Out-parameters come back as extra return values.
local shop = vector3(25.7, -1347.3, 29.5)
CreateThread(function()
while true do
local sleep = 1000
local dist = #(GetEntityCoords(PlayerPedId()) - shop)
if dist < 20.0 then
sleep = 0
DrawMarker(1, shop.x, shop.y, shop.z - 1.0, 0, 0, 0, 0, 0, 0, 1.5, 1.5, 0.5, 124, 92, 255, 150, false, false, 2, false, nil, nil, false)
if dist < 1.5 and IsControlJustPressed(0, 38) then
TriggerServerEvent('shop:open')
end
end
Wait(sleep)
end
end)Wait(0)runs every frame — only while drawing or reading controls.- Far from anything interesting, sleep for 500–1000 ms or more.
- Better still, replace loops with ox_lib points and zones or events.
SetTimeout(ms, fn)runs something once after a delay.
Chapter 07
Where to keep state: state bags, convars, KVP, database
Most “it works for me but not for others” bugs are state kept in the wrong place. Pick the store by who needs to see it and how long it must live.
| Store | Scope | Lifetime | Use for |
|---|---|---|---|
| Local Lua variable/table | One side, one resource | Until resource restart | Caches, runtime data |
State bag (GlobalState, Player(src).state, Entity(ent).state) | Synced to clients that can see it | Until restart or entity deletion | Duty status, vehicle fuel, door state |
Convar (GetConvar) | Server; setr replicates to clients | Set in server.cfg | Configuration and secrets (set only) |
KVP (SetResourceKvp) | Per resource, on the client PC or the server | Persistent | Small preferences and flags |
| Database (oxmysql) | Server | Persistent | Anything valuable or shared |
-- server
Entity(veh).state:set('fuel', 64.0, true)
Player(src).state:set('onDuty', true, true)
-- client
AddStateBagChangeHandler('fuel', nil, function(bagName, key, value)
local ent = GetEntityFromStateBagName(bagName)
if ent ~= 0 then SetVehicleFuelLevel(ent, value) end
end)Chapter 08
Entities, network IDs and server-side spawning
Entity handles are local numbers; network IDs are what you send between client and server. Spawning important entities on the server makes them reliable and lets you lock the client out.
local function spawnFor(src, model, coords)
local veh = CreateVehicleServerSetter(model, 'automobile', coords.x, coords.y, coords.z, coords.w)
while not DoesEntityExist(veh) do Wait(0) end
SetVehicleNumberPlateText(veh, 'NC' .. math.random(1000, 9999))
SetPedIntoVehicle(GetPlayerPed(src), veh, -1)
return NetworkGetNetworkIdFromEntity(veh)
endlocal veh = NetworkGetEntityFromNetworkId(netId)
if DoesEntityExist(veh) then
SetVehicleEngineOn(veh, true, true, false)
end- Never send entity handles over events — they mean different things on each machine.
- Each networked entity has an owner that simulates it; others must request control to change it.
- Delete what you create: on job end, on
playerDroppedand ononResourceStop. - Once every script spawns server-side, turn on
sv_entityLockdown.
Chapter 09
The database with oxmysql
oxmysql is the standard database resource. Use its awaitable functions, always pass values as parameters, and design tables around the questions you ask.
| Function | Returns |
|---|---|
MySQL.query.await(sql, params) | All rows |
MySQL.single.await(sql, params) | The first row, or nil |
MySQL.scalar.await(sql, params) | The first column of the first row |
MySQL.insert.await(sql, params) | The new row’s ID |
MySQL.update.await(sql, params) | Number of affected rows |
MySQL.prepare.await(sql, params) | Prepared statement result — fast for repeated queries |
MySQL.transaction.await(queries) | true if every query succeeded |
local row = MySQL.single.await('SELECT money FROM characters WHERE id = ?', { charId })
local changed = MySQL.update.await(
'UPDATE characters SET money = money - ? WHERE id = ? AND money >= ?',
{ price, charId, price }
)
if changed == 0 then return false end -- not enough money, nothing changed- Primary key on every table; index every column you filter or join on.
utf8mb4everywhere so names with emoji do not break inserts.- JSON columns for small flexible data; real columns for anything you search or sum.
- No queries inside per-player or per-frame loops — batch them.
Chapter 10
Frameworks: ESX, QBCore, Qbox and ox_core
Frameworks give you a player object, jobs and money. The ideas are the same everywhere; only the function names differ.
| Task | ESX Legacy | QBCore | Qbox |
|---|---|---|---|
| Get player | ESX.GetPlayerFromId(src) | QBCore.Functions.GetPlayer(src) | exports.qbx_core:GetPlayer(src) |
| Stable ID | xPlayer.identifier | Player.PlayerData.citizenid | player.PlayerData.citizenid |
| Add cash | xPlayer.addMoney(n) | Player.Functions.AddMoney('cash', n) | exports.qbx_core:AddMoney(src, 'cash', n) |
| Remove bank | xPlayer.removeAccountMoney('bank', n) | Player.Functions.RemoveMoney('bank', n) | exports.qbx_core:RemoveMoney(src, 'bank', n) |
| Job | xPlayer.job.name, .grade | PlayerData.job.name, .job.grade.level | Same as QBCore |
| Set job | xPlayer.setJob(job, grade) | Player.Functions.SetJob(job, grade) | exports.qbx_core:SetJob(src, job, grade) |
| Loaded event (client) | esx:playerLoaded | QBCore:Client:OnPlayerLoaded | QBCore:Client:OnPlayerLoaded |
ox_core is different: characters, groups and bank accounts through Ox.GetPlayer(source) and Ox.GetCharacterAccount(charId), with cash and items as ox_inventory items. To support several frameworks from one script, detect the running one with GetResourceState and route through a small bridge.
Bridge = {}
if GetResourceState('qbx_core') == 'started' then
function Bridge.addCash(src, n) return exports.qbx_core:AddMoney(src, 'cash', n) end
elseif GetResourceState('qb-core') == 'started' then
local QBCore = exports['qb-core']:GetCoreObject()
function Bridge.addCash(src, n) return QBCore.Functions.GetPlayer(src).Functions.AddMoney('cash', n) end
elseif GetResourceState('es_extended') == 'started' then
local ESX = exports.es_extended:getSharedObject()
function Bridge.addCash(src, n) ESX.GetPlayerFromId(src).addMoney(n) return true end
endChapter 11
ox_lib, ox_target and zones
The Overextended libraries remove most boilerplate: callbacks, menus, notifications, progress bars, zones, points, caching and interaction targets.
lib.notify({ title = 'Garage', description = 'Vehicle stored', type = 'success' })
if lib.progressBar({ duration = 3000, label = 'Repairing', canCancel = true }) then
-- finished
end
local input = lib.inputDialog('Transfer', { { type = 'number', label = 'Amount', min = 1 } })
local zone = lib.zones.box({
coords = vec3(215.0, -810.0, 30.7), size = vec3(6, 6, 3), rotation = 45,
onEnter = function() lib.showTextUI('[E] Garage') end,
onExit = function() lib.hideTextUI() end,
})
local ped = cache.ped -- cached player ped, updated for youexports.ox_target:addModel(`prop_atm_01`, {
{
name = 'atm:use',
label = 'Use ATM',
icon = 'fa-solid fa-credit-card',
distance = 1.5,
onSelect = function() TriggerServerEvent('bank:openAtm') end,
},
})Zones, targets and text UI run on the client — the server still checks the player is really there before doing anything that matters.
Chapter 12
Items and inventories
Almost every script gives, takes or checks items. A thin inventory layer keeps the rest of your code identical on ox_inventory, qb-inventory and ESX.
| Operation | ox_inventory | qb-inventory | ESX default |
|---|---|---|---|
| Add | AddItem(src, name, n, meta) | AddItem(src, name, n, slot, info, reason) | xPlayer.addInventoryItem(name, n) |
| Remove | RemoveItem(src, name, n) | RemoveItem(src, name, n, slot, reason) | xPlayer.removeInventoryItem(name, n) |
| Count | GetItemCount(src, name) | GetItemCount(src, name) | xPlayer.getInventoryItem(name).count |
| Can carry | CanCarryItem(src, name, n) | CanAddItem(src, name, n) | xPlayer.canCarryItem(name, n) |
- Take the cost first; give the reward only if the removal succeeded.
- Check carry capacity before adding, or handle the failure.
- Item names in config — servers rename items.
- ox_inventory officially supports ox_core, ESX, Qbox and ND_Core; on QBCore, qb-inventory is the default.
Weapons, attachments and ammo live in the inventory too — the FiveM Weapons Handbook covers them in depth.
Chapter 13
NUI: building interfaces
NUI is a Chromium-based browser layer over the game. Any HTML, CSS and JavaScript works — most modern resources use React or another framework built with Vite.
local open = false
local function setOpen(state)
open = state
SetNuiFocus(state, state)
SendNUIMessage({ action = 'visible', data = state })
end
RegisterCommand('bank', function() setOpen(true) end)
RegisterNUICallback('close', function(_, cb)
setOpen(false)
cb('ok')
end)
AddEventHandler('onResourceStop', function(res)
if res == GetCurrentResourceName() and open then SetNuiFocus(false, false) end
end)export async function fetchNui<T>(event: string, data?: unknown): Promise<T> {
const res = await fetch(`https://${GetParentResourceName()}/${event}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json; charset=UTF-8' },
body: JSON.stringify(data ?? {}),
});
return res.json();
}
window.addEventListener('message', (e) => {
const { action, data } = e.data;
if (action === 'visible') setVisible(data);
});- Build to static files (
web/dist), list them infilesand pointui_pageatindex.html. - Use relative asset paths — the page is served from the resource, not a web server root.
- Avoid
backdrop-filter; the in-game browser does not render it reliably. Use solid semi-transparent colours. - Every NUI callback always calls
cb, or the page’sfetchnever resolves. - Validate on the server — players can call NUI callbacks with any data from devtools.
More: NUI to Lua patterns, NUI development, and the NUI chapters of the FiveM Server Launch Handbook.
Chapter 14
Writing code that cannot be exploited
Every net event is a public API that anyone connected can call with any arguments. Write each handler as if a cheater will call it — because one will.
local SHOPS = { legion = vector3(25.7, -1347.3, 29.5) }
local PRICES = { water = 5, bread = 8 }
local last = {}
RegisterNetEvent('shop:buy', function(shopId, item, amount)
local src = source
if type(shopId) ~= 'string' or type(item) ~= 'string' or type(amount) ~= 'number' then return end
amount = math.floor(amount)
if amount < 1 or amount > 20 then return end
local shop, price = SHOPS[shopId], PRICES[item]
if not shop or not price then return end
if #(GetEntityCoords(GetPlayerPed(src)) - shop) > 5.0 then return end
local now = GetGameTimer()
if last[src] and now - last[src] < 1000 then return end
last[src] = now
local total = price * amount
if not Bridge.removeCash(src, total) then return end
exports.ox_inventory:AddItem(src, item, amount)
end)
AddEventHandler('playerDropped', function() last[source] = nil end)| Check | Why |
|---|---|
Copy source into a local first | It changes after any Wait or await |
| Types and ranges | Cheats send strings, negatives, NaN and huge numbers |
| Look up prices and rewards on the server | Never trust a price the client sent |
| Distance, job, item, cooldown | The player must actually be able to do this now |
| Rate limit | Stops spam and dupes through repeated calls |
| Log refusals | Repeated refusals identify cheaters |
- Server-only events:
AddEventHandlerwithoutRegisterNetEvent. - SQL through placeholders only.
- Secrets in server-only convars (
set), neversetr/setsor client files. - Layers beat a single anticheat: server authority, validation, entity lockdown, game event filtering, identifier + token bans.
Chapter 15
Performance and memory
A good resource idles near 0.00 ms in resmon. Measure first, fix the worst loop, measure again.
| resmon CPU (client) | Verdict |
|---|---|
| 0.00–0.02 ms idle | What every idle resource should show |
| Up to ~0.2 ms while active | Fine for UI and interaction |
| Consistently above ~0.5 ms | Investigate — players will notice on busy servers |
- Adaptive
Wait— long sleeps when nothing is near. - Events, state bags and zones instead of polling.
#(a - b)for distances; backtick hashes instead ofGetHashKeyin loops.- Read the ped and coords once per tick (or use ox_lib’s
cache). - No new tables or strings in per-frame code.
- Batch database writes; never query per frame.
CreateThread(function()
while true do
print(('[%s] %.1f KB'):format(GetCurrentResourceName(), collectgarbage('count')))
Wait(60000)
end
end)Growing memory almost always means per-player tables not cleared on playerDropped, handlers registered inside other handlers, unbounded caches, or entities and blips never deleted.
Chapter 16
Debugging and common errors
Client errors show in F8, server errors in the server console or txAdmin’s live console. The message tells you the file and line — read it before changing code.
| Error | Meaning | Usual fix |
|---|---|---|
attempt to index a nil value | Used . or [] on nil | The object was never set — check the lookup before it |
attempt to call a nil value | The function does not exist | Typo, wrong side (client/server), or missing dependency |
attempt to perform arithmetic on a nil value | A number you used is nil | Validate inputs; give defaults |
attempt to concatenate a nil value | .. with nil | Use tostring() or check first |
No such export X in resource Y | Export missing or resource not started | Check the name and start order |
Couldn’t find resource | Wrong name in ensure | Folder name, not the manifest name |
Syntax error near end | Unbalanced if/function/end | Let LuaLS highlight it |
- Print with context:
print(('[garage] store %s by %d'):format(plate, src)). lib.print.debugwith a debug convar keeps logs out of production.- Reproduce on the local server before touching the live one.
Chapter 17
HTTP APIs, Discord webhooks and scheduled jobs
Servers talk to the outside world: Discord logs, stores, whitelists, stats. Do it from the server, keep keys private and never block the server thread.
local HOOK = GetConvar('logs_webhook', '')
local function log(title, text, color)
if HOOK == '' then return end
PerformHttpRequest(HOOK, function(status)
if status == 429 then print('[logs] rate limited') end
end, 'POST', json.encode({
embeds = { {
title = title,
description = text,
color = color or 8150271,
timestamp = os.date('!%Y-%m-%dT%H:%M:%SZ'),
} },
}), { ['Content-Type'] = 'application/json' })
endlib.cron.new('0 6 * * *', function()
MySQL.update.await('UPDATE characters SET daily_claimed = 0')
end)PerformHttpRequest(url, cb(status, body, headers, err), method, data, headers)— wrap it in apromise+Citizen.Awaitfor a synchronous style.- Webhook URLs and API keys in
setconvars only. - Respect Discord limits (for example 4096 characters per description, 10 embeds per message) and back off on 429.
SetTimeoutfor one-off delays, loops withWaitfor intervals,lib.cronfor clock times.
Chapter 18
Config, locales, documentation and review
What separates a script people recommend from one they refund: a config owners understand, their language, docs that answer questions before they are asked, and code someone else checked.
Config = {}
Config.Locale = 'en'
Config.Framework = 'auto' -- auto | qbx | qb | esx
Config.Inventory = 'auto' -- auto | ox | qb | esx
Config.Garages = {
legion = { label = 'Legion Square', coords = vec4(215.8, -810.1, 30.7, 157.0), jobs = false },
mrpd = { label = 'Mission Row PD', coords = vec4(452.3, -996.1, 25.8, 90.0), jobs = { police = 0 } },
}- Secrets and reward amounts in a server-only config.
- Validate the config on start and print readable errors.
- ox_lib locales:
ox_lib 'locale'in the manifest,locales/en.json,locale('key'); owners choose withsetr ox:locale de. - Docs: requirements, install steps with
ensureorder, every config option, commands, exports and events, troubleshooting, changelog. - LuaLS annotations (
---@param,---@class) catch bugs before players do.
Before every release, run the code review checklist: security, performance, cleanup, compatibility, usability.
Chapter 19
Shipping and selling: escrow, Tebex, pricing, clients
Selling FiveM scripts runs through Tebex and the Cfx.re Portal’s asset escrow. Know the rules before you build a business on them.
| Fact | Detail |
|---|---|
| What escrow encrypts | Lua, YFT, YDD and YDR files |
| What stays readable | Files listed in escrow_ignore, plus NUI files |
| Delivery | Through Tebex; buyers receive the asset in their Cfx.re Portal account |
| Without the entitlement | The server refuses to start it: “You lack the required entitlement” |
| Upload size limit | 1 GB per asset |
| Monetization | Paid FiveM content is sold through Tebex under the platform’s licence terms |
- Create a Tebex account, verify it, and link your Cfx.re account.
- Upload the zipped resource to the Portal to escrow it.
- Create a Tebex package that delivers the escrowed asset.
- Launch with a video, documentation and a support channel.
Pricing, contracts and portfolios: script pricing, freelance contracts, developer portfolio.
Chapter 20
Your learning path
Learn in this order and each step builds on the last. Skipping ahead is why so many scripts work on a test server and break on a live one.
| Stage | Learn | Build |
|---|---|---|
| 1 | Lua, resources, your first script | A command that spawns a car and prints its plate |
| 2 | Client vs server, events, natives, threads | A delivery job paid by the server |
| 3 | A framework, ox_lib, oxmysql | A garage that stores vehicles in the database |
| 4 | NUI | A React menu for the garage |
| 5 | Security and performance | Harden it; get it to 0.00 ms idle |
| 6 | Git, local server, LuaLS | Version it; test like a professional |
| 7 | Docs, escrow, Tebex, portfolio | Release it — free first, paid later |
Where to learn more: the official FiveM docs, the natives reference, the Lua 5.4 manual, framework docs and the Cfx.re forum.
Checklist
- I declare every variable
local - My client never decides money, items or permissions
- Every net event validates
source, types, ranges, distance and cooldown - All SQL uses placeholders
- My idle resources show 0.00–0.02 ms in resmon
- I clean up on
playerDroppedandonResourceStop - My code is in Git and my secrets are not
Reference
Cheat sheets
The functions you will look up again and again.
Events and communication
Which function to call, from which side.
| Function | Side | What it does |
|---|---|---|
RegisterNetEvent(name, fn) | Both | Handle an event that may arrive over the network |
AddEventHandler(name, fn) | Both | Handle a local event |
TriggerEvent(name, ...) | Both | Fire on the same side |
TriggerServerEvent(name, ...) | Client | Send to the server |
TriggerClientEvent(name, id, ...) | Server | Send to one player, or -1 for all |
TriggerLatentClientEvent(name, id, bps, ...) | Server | Send large data without flooding the connection |
lib.callback.register / lib.callback.await | Both | Request and answer (ox_lib) |
exports('Name', fn) / exports.res:Name() | Same side | Call functions across resources |
SendNUIMessage(table) / RegisterNUICallback | Client | Talk to and from the NUI page |
Lifecycle events
Hooks for setup and cleanup.
| Event | Side | Fires when |
|---|---|---|
onResourceStart | Both | A resource starts (check the name argument) |
onResourceStop | Both | A resource stops — clean up here |
playerConnecting | Server | A player is joining — deferrals, whitelists, bans |
playerJoining | Server | The player has a server ID and is loading in |
playerDropped | Server | A player left — clear their data |
txAdmin:events:scheduledRestart | Server | Before a scheduled restart |
entityCreating | Server | An entity is about to be created — cancel to block |
oxmysql at a glance
All awaitable on the server; placeholders always.
| Call | Returns |
|---|---|
MySQL.query.await(sql, p) | Rows |
MySQL.single.await(sql, p) | One row or nil |
MySQL.scalar.await(sql, p) | One value |
MySQL.insert.await(sql, p) | Insert ID |
MySQL.update.await(sql, p) | Affected rows |
MySQL.prepare.await(sql, p) | Prepared result |
MySQL.transaction.await(list) | true / false |
Natives you will use constantly
Client unless marked server.
| Native | Use |
|---|---|
PlayerPedId() / GetPlayerPed(src) (server) | The player’s ped |
GetEntityCoords(ent) / GetEntityHeading(ent) | Position and facing |
GetPlayerIdentifierByType(src, 'license') (server) | A stable player ID |
IsPlayerAceAllowed(src, 'object') (server) | Permission check |
RequestModel(hash) / HasModelLoaded(hash) | Load a model before spawning |
NetworkGetNetworkIdFromEntity / NetworkGetEntityFromNetworkId | Convert handles and network IDs |
SetNuiFocus(hasFocus, hasCursor) | Give NUI keyboard and mouse |
RegisterKeyMapping(cmd, desc, 'keyboard', 'E') | Rebindable key for a command |
GetConvar(name, default) | Read a convar |
GetGameTimer() | Milliseconds — for cooldowns |
Reference
Glossary
- CfxLua
- FiveM’s Lua 5.4 fork with vectors, quaternions and compile-time backtick hashes.
- Callback
- A request that gets an answer from the other side —
lib.callback, ESX and QBCore callbacks. - Client script
- Code that runs on each player’s PC; never trusted.
- Convar
- A configuration variable set in server.cfg and read with
GetConvar. - Deferrals
- The
playerConnectingAPI for holding a joining player while you run checks. - Entity
- A ped, vehicle or object in the game world.
- Entity lockdown
sv_entityLockdown— stops clients creating entities.- Escrow
- Cfx.re’s encryption and entitlement system for paid resources.
- Event
- A named message between scripts or between client and server.
- Export
- A function one resource makes callable by others on the same side.
- fxmanifest.lua
- The file that declares a resource’s scripts, files and dependencies.
- GlobalState
- The server-wide state bag.
- Hash
- The number the game uses for a model or name;
`adder`in CfxLua. - KVP
- Key-value storage per resource via
SetResourceKvp. - LuaLS
- The Lua Language Server that powers VS Code completion and diagnostics.
- Native
- A built-in game or Cfx.re function such as
GetEntityCoords. - Network ID
- The ID of a networked entity that is the same on every machine.
- NUI
- The in-game browser layer for interfaces.
- NUI callback
- A Lua handler the page calls with
fetch('https://resource/name'). - OneSync
- Server-side state awareness; enables server entities and state bags.
- Owner
- The client that simulates a networked entity.
- ox_lib
- Overextended’s library of UI, callbacks, zones, cron and helpers.
- ox_target
- Overextended’s interaction targeting system.
- oxmysql
- The database connector with
MySQL.*.awaitfunctions. - Placeholder
- A
?or@namein SQL that keeps values as data. - Resource
- A folder with an fxmanifest.lua — the unit FiveM starts and stops.
- resmon
- The in-game resource monitor (
resmon 1in F8). - Routing bucket
- An instance of the world; players and entities in different buckets do not see each other.
- Server script
- Code that runs only on the server; the authority.
- source
- The ID of the player that sent a net event, inside its handler.
- State bag
- Synced key-value data on the server, a player or an entity.
- Tebex
- The store platform FiveM content is sold through.
- Thread
- A coroutine started with
CreateThread, paused withWait. - Zone
- An area (box, sphere, poly) that triggers enter/exit/inside code.
Reference
Questions developers ask
What language are FiveM scripts written in?
Mostly Lua (CfxLua, based on Lua 5.4). JavaScript/TypeScript and C# are also supported.
How do I start FiveM scripting?
Install VS Code with the Lua Language Server, run a local FXServer, create a resource with an fxmanifest.lua and a client and server script, and ensure it.
What is the difference between client and server scripts?
Client scripts run on each player’s PC and handle what they see and do; server scripts run on the server and own money, items, the database and permissions.
How do I send data from client to server?
TriggerServerEvent('name', ...) on the client and RegisterNetEvent('name', function(...) end) on the server, reading source for the sender.
How do I get a value back from the server?
Use a callback: lib.callback.register on the server and lib.callback.await on the client (or your framework’s callbacks).
What are natives?
The game’s built-in functions, listed at docs.fivem.net/natives and called in PascalCase, like GetEntityCoords.
Why does my script use so much resmon?
Usually a Wait(0) loop running when it does not need to. Sleep longer when nothing is near, or replace the loop with events or zones.
How do I use the database in FiveM?
With oxmysql: MySQL.query.await('SELECT ... WHERE id = ?', { id }) on the server, always with placeholders.
How do I make my script work on ESX and QBCore?
Detect the framework with GetResourceState and route money, job and item calls through a small bridge.
How do I make a NUI in FiveM?
Build an HTML/React page, list it with ui_page and files, send data with SendNUIMessage, receive calls with RegisterNUICallback, and manage focus with SetNuiFocus.
How do I stop cheaters triggering my events?
Validate everything in the server handler: types, ranges, server-side prices, distance, permissions and cooldowns — and never trust client values.
What are state bags?
Synced key-value data on the server, players or entities, set on the server and read or watched on clients.
How do I sell FiveM scripts?
Escrow the resource through the Cfx.re Portal and sell it on Tebex with documentation and support.
What does escrow encrypt?
Lua, YFT, YDD and YDR files; config files you list in escrow_ignore and NUI files stay readable.
Should I learn TypeScript for FiveM?
Learn Lua first; add TypeScript for NUI and for server code that benefits from npm packages.
How do I debug a FiveM script?
Read the error in F8 (client) or the server console, check the file and line, add printed context, and reproduce on a local server.
Where can I learn FiveM development?
docs.fivem.net, the natives reference, the Lua manual, framework docs, the Cfx.re forum — and the detailed guides linked in each chapter.
How long does it take to become a FiveM developer?
Simple scripts take weeks; secure, fast, sellable resources usually take months of steady practice.
Reference
Official references
Primary sources this handbook is checked against.
Official Cfx.re documentation
- Scripting manual — Start here
- Lua runtime (CfxLua) — Vectors, hashes, exports, libraries
- Resource manifest — fxmanifest.lua reference
- State bags — Synced data
- Convars — Reading configuration
- Natives reference — Every native
- Asset escrow — Protecting paid resources
Libraries and frameworks
- Overextended docs — ox_lib, ox_inventory, ox_target, oxmysql
- oxmysql on GitHub — Database connector
- ESX documentation — ESX Legacy
- QBCore documentation — qb-core
- Qbox documentation — qbx_core
Language
- Lua 5.4 reference manual — The language itself
- Programming in Lua — The book, first edition online
Community
- Cfx.re forum — Releases and help
- Cfx.re Portal — Keys, assets and escrow uploads
The FiveM Handbook series
Four free handbooks, one per part of the job.
Need this built, not just explained?
Custom FiveM resources, NUI and security reviews
Lua and React NUI for ESX, QBCore and Qbox, OneSync performance audits, and the websites and SEO around your server brand.