Skip to Content
ScriptingEvents

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

DirectionTriggerHandler
Client → clientTriggerEventAddEventHandler
Server → serverTriggerEventAddEventHandler
Client → serverTriggerServerEventRegisterNetEvent
Server → one clientTriggerClientEvent(name, playerSource, ...)RegisterNetEvent
Server → all clientsTriggerClientEvent(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' }) end

Register 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:

  1. Validate parameter types, ranges, and allowed values.
  2. Read money, inventory, permissions, and progression from trusted server state.
  3. Verify the player’s server-side position for world interactions.
  4. Confirm the requested entity or record belongs to the player.
  5. Apply an operation-specific rate limit.
  6. 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:

  1. Confirm the resource containing both sides has started.
  2. Confirm the event name, capitalization, and direction match.
  3. Log immediately before the trigger and at the first line of the handler.
  4. Validate the payload before assuming the transport failed.
  5. Check the client F8 console and server console for runtime errors.
  6. 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.