FiveM Events
FiveM events let code communicate within one runtime or across the client/server network boundary. Use local events for same-context communication and network events only when data must cross between a client and the server.
Choose the correct event API
| Direction | Trigger | Handler |
|---|---|---|
| Client → client | TriggerEvent | AddEventHandler |
| Server → server | TriggerEvent | AddEventHandler |
| Client → server | TriggerServerEvent | RegisterNetEvent |
| Server → one client | TriggerClientEvent(name, playerSource, ...) | RegisterNetEvent |
| Server → all clients | TriggerClientEvent(name, -1, ...) | RegisterNetEvent |
Do not register an event as networked if it never needs to cross contexts. A networked event adds an attack surface that a modified client may invoke.
Local events
TriggerEvent stays in the current context. A client triggers client handlers;
a server script triggers server handlers.
AddEventHandler('myresource:cacheReady', function(entryCount)
print(('Cache ready with %d entries'):format(entryCount))
end)
TriggerEvent('myresource:cacheReady', 42)Use local events for resource lifecycle hooks, internal modules, and extension points that do not require networking.
Client to server events
Client:
TriggerServerEvent('myresource:requestOpenDoor', 12)Server:
RegisterNetEvent('myresource:requestOpenDoor', function(doorId)
local playerSource = source
if type(doorId) ~= 'number' or doorId % 1 ~= 0 then
return
end
local door = Config.Doors[doorId]
if not door then
return
end
local ped = GetPlayerPed(playerSource)
if ped == 0 then
return
end
local playerCoords = GetEntityCoords(ped)
if #(playerCoords - door.coords) > 2.0 then
return
end
TriggerClientEvent('myresource:setDoorState', -1, doorId, true)
end)On the server, source is the player who sent the current client-to-server
event. Copy it into a local variable before using an asynchronous callback.
Server to client events
Send only to the player who needs the data:
-- server.lua
local function showProfileReady(targetPlayer)
TriggerClientEvent('myresource:showMessage', targetPlayer, {
text = 'Your profile is ready',
level = 'success'
})
endRegister the handler on the client:
-- client.lua
RegisterNetEvent('myresource:showMessage', function(message)
if type(message) ~= 'table' or type(message.text) ~= 'string' then
return
end
print(message.text)
end)Use -1 only when every connected client needs the same event. Broadcasting
large or frequent payloads wastes bandwidth and serialization work.
RegisterNetEvent security
RegisterNetEvent allows an event to cross the network boundary; it does not
authenticate the caller or validate the payload.
For every client-to-server event:
- Validate parameter types, ranges, and allowed values.
- Read money, inventory, permissions, and progression from trusted server state.
- Verify the player’s server-side position for world interactions.
- Confirm the requested entity or record belongs to the player.
- Apply an operation-specific rate limit.
- Log rejections without logging secrets or excessive personal data.
Never accept a client-supplied reward:
-- Unsafe: the client chooses the amount.
TriggerServerEvent('myresource:addMoney', 1000000)Send the requested action instead and calculate the result on the server:
-- Client
TriggerServerEvent('myresource:completeDelivery', deliveryId)-- Server
RegisterNetEvent('myresource:completeDelivery', function(deliveryId)
local playerSource = source
local delivery = ActiveDeliveries[playerSource]
if not delivery or delivery.id ~= deliveryId then
return
end
local reward = Config.Deliveries[delivery.routeId].reward
-- Apply reward through the server-side framework API here.
end)Block the wrong network context
Some client handlers should accept only events sent by the server. According to
Cfx.re’s event-security guidance, the server uses network ID 65535 as the
source for a server-originated client event:
RegisterNetEvent('myresource:trustedClientUpdate', function(data)
if source ~= 65535 then
return
end
-- Process data sent by the server.
end)This check is for event context. It does not make client state trustworthy.
Rate-limit expensive events
Rate limits should be specific to the operation and cleaned up when a player disconnects.
local windows = {}
local WINDOW_MS = 5000
local MAX_REQUESTS = 10
local function isRateLimited(playerSource, eventName)
local now = GetGameTimer()
local key = ('%s:%s'):format(playerSource, eventName)
local window = windows[key]
if not window or now - window.startedAt >= WINDOW_MS then
windows[key] = {
startedAt = now,
count = 1
}
return false
end
window.count = window.count + 1
return window.count > MAX_REQUESTS
end
RegisterNetEvent('myresource:search', function(query)
local playerSource = source
if isRateLimited(playerSource, 'search') then
return
end
if type(query) ~= 'string' or #query > 80 then
return
end
-- Perform the bounded server-side search.
end)
AddEventHandler('playerDropped', function()
local prefix = ('%s:'):format(source)
for key in pairs(windows) do
if key:sub(1, #prefix) == prefix then
windows[key] = nil
end
end
end)A fixed example limit is not a universal safe value. Choose thresholds from the normal frequency and cost of the specific operation.
Event payload performance
Event arguments are serialized. Keep payloads small and stable:
- Send identifiers and the fields the receiver actually needs.
- Avoid large nested tables, HTML strings, and repeated unchanged state.
- Do not trigger network events from per-frame loops.
- Prefer one targeted client over a broadcast.
- Use state bags for replicated state that multiple resources observe over time.
Debug events
When an event appears not to fire:
- Confirm the resource containing both sides has started.
- Confirm the event name, capitalization, and direction match.
- Log immediately before the trigger and at the first line of the handler.
- Validate the payload before assuming the transport failed.
- Check the client F8 console and server console for runtime errors.
- Use resmon and the profiler when event volume affects performance.
PerformHttpRequest is an asynchronous HTTP function, not a network event. Its
callback receives the HTTP status, body, and headers. Search it in the
official native reference for the current
signature.