Frameworks
Callbacks: asking the server a question and getting an answer
How callbacks work in FiveM: why events alone cannot return values, ox_lib lib.callback (client to server and server to client), ESX.RegisterServerCallback/TriggerServerCallback, QBCore CreateCallback/TriggerCallback, writing your own with request IDs, and securing callbacks.
Overview
Events are one-way: the client tells the server something and gets nothing back. But scripts constantly need answers — how much money do I have, is this garage slot free, can I open this door? A callback is an event pair with a reply: the client asks, the server answers, and the client code continues with the result.
Why events cannot return values
TriggerServerEvent sends a message and returns immediately; the server handler runs later, on another machine. To get an answer, the server must send a second event back, and the client must match that reply to the question. Callback helpers do exactly that for you. Events themselves are covered in events explained.
ox_lib callbacks
lib.callback.register('garage:getVehicles', function(source)
local src = source
local id = GetPlayerIdentifierByType(src, 'license')
return MySQL.query.await('SELECT plate, model FROM player_vehicles WHERE license = ?', { id })
end)-- waits for the answer (inside a thread / command / event handler)
local vehicles = lib.callback.await('garage:getVehicles', false)
print(#vehicles .. ' vehicles')
-- or with a function, without waiting
lib.callback('garage:getVehicles', false, function(result)
print(#result)
end)The second argument is a client-side rate limit in milliseconds (false for none). ox_lib also supports the reverse: the server calls lib.callback.await('name', playerId, ...) and the client answers with lib.callback.register — useful to ask a client for something only it knows, like its current waypoint. More about the library: ox_lib guide.
ESX callbacks
ESX.RegisterServerCallback('bank:getBalance', function(source, cb)
local xPlayer = ESX.GetPlayerFromId(source)
cb(xPlayer and xPlayer.getAccount('bank').money or 0)
end)ESX.TriggerServerCallback('bank:getBalance', function(balance)
ESX.ShowNotification(('Balance: $%d'):format(balance))
end)QBCore callbacks
QBCore.Functions.CreateCallback('bank:getBalance', function(source, cb)
local Player = QBCore.Functions.GetPlayer(source)
cb(Player and Player.PlayerData.money.bank or 0)
end)QBCore.Functions.TriggerCallback('bank:getBalance', function(balance)
QBCore.Functions.Notify(('Balance: $%d'):format(balance), 'success')
end)How it works underneath
Every helper follows the same pattern: give each request an ID, send it with the arguments, and resolve a promise when a reply with that ID arrives.
local pending, nextId = {}, 0
function TriggerCallback(name, ...)
nextId = nextId + 1
local p = promise.new()
pending[nextId] = p
TriggerServerEvent('cb:request', name, nextId, ...)
return Citizen.Await(p)
end
RegisterNetEvent('cb:response', function(id, ...)
local p = pending[id]
if p then pending[id] = nil; p:resolve({ ... }) end
end)Use a maintained library in real code; this shows why a callback is just two events and a promise.
Securing callbacks
- Anyone can call any registered callback with any arguments — check types, ownership and permissions.
- Return only data the player may see (their own vehicles, not everyone’s).
- Do heavy database work once and cache, not on every UI refresh.
- Handle
nilresults on the client; a callback can fail.
The full checklist is in secure server events.
Frequently asked questions
What is a callback in FiveM?
A request/response pair: one side calls a named handler on the other side and receives its return value.
How do I get a value from the server in FiveM?
Use a callback — for example lib.callback.await('name', false) on the client with lib.callback.register on the server.
Can the server call a client callback?
Yes, with ox_lib: lib.callback.await('name', playerId, ...) on the server and lib.callback.register on the client.
Why does lib.callback.await error outside a thread?
Awaiting needs a coroutine. Call it inside a thread, command, event handler or NUI callback.
Need this built, not just explained?
Ten years of FiveM work, from Lua to NUI
Custom resources, React NUI, ESX / QBCore / Qbox integration, OneSync performance audits and security reviews — plus the websites and SEO around your server brand.
Related guides
- FrameworksGetting started with ox_libStart ox_lib before your resource, add shared_script '@ox_lib/init.lua' and lua54 'yes' to your fxmanifest, and the global lib becomes available. Use cache.ped/cache.vehicle instead of calling natives in loops, lib.callback for client↔server requests, lib.notify, lib.progressBar, lib.registerContext/lib.showContext, lib.inputDialog, lib.points and lib.zones for interaction, and lib.addCommand for commands with ACE restrictions.
- EngineeringEvents in FiveM: how resources and players talkHandle events with AddEventHandler(name, fn); add RegisterNetEvent(name) (or use RegisterNetEvent(name, fn)) to allow the event to arrive over the network. TriggerEvent fires locally on the same side, TriggerServerEvent sends from a client to the server, and TriggerClientEvent(name, target, ...) sends from the server to one player (target = player ID) or everyone (-1). On the server, source is the sending player.
- EngineeringWriting server events that cannot be abusedAny client can call any event you registered with RegisterNetEvent, with any arguments. In each handler: copy source into a local, check argument types and ranges, re-derive everything from server state (prices, amounts, rewards), verify the player can do this now (distance, job, item, cooldown), and log refusals. Events only other server scripts should use are registered with AddEventHandler alone, so clients cannot trigger them.
- EngineeringThreads and Wait in FiveM: loops that do not eat framesCreateThread(fn) starts a coroutine that runs alongside the game; Wait(ms) pauses it and lets everything else run. Wait(0) resumes on the next frame, so the loop runs every frame (60+ times a second). Use it only while you must draw or read input every frame; otherwise sleep for hundreds of milliseconds, and make loops adaptive — fast when the player is near something, slow when they are not.