FiveM Client vs. Server Events: How to Secure UI Actions

The client is controlled by the player. That means every value sent from a client, browser UI or client resource is a request—not proof that the action is allowed.
This distinction matters for every FiveM server, especially resources with menus. A button that says “buy,” “give,” “save,” “teleport” or “open admin layout” must eventually reach a server-side authority that validates the request.
AddEventHandler and RegisterNetEvent are different tools
Use a local event handler when an event should stay inside the same execution context. Use a networked event only when the other side genuinely needs to receive it. Cfx.re's security guidance warns against registering server events as networked when they should be internal.
That first boundary reduces the number of entry points an attacker can reach. It does not replace validation for events that clients are allowed to request.
The insecure pattern
This handler trusts the client to decide how much money to award:
RegisterNetEvent('shop:buyReward', function(amount)
local player = getPlayer(source)
player.addMoney(amount)
end)
The client can call the event with any amount. Hiding the button in the UI does not change that.
A safer request pattern
The client should send an identifier. The server should load the price and validate the player:
RegisterNetEvent('shop:buyReward', function(itemName)
local src = source
local item = Config.Items[itemName]
if not item then return end
if not isNearShop(src, item.shop) then return end
if not hasFunds(src, item.price) then return end
removeFunds(src, item.price)
giveItem(src, item.name, item.quantity)
end)
The exact framework API changes, but the principle does not: derive sensitive values from server-owned configuration and state.
Validate the whole request
For a state-changing event, ask:
- Is the source player still connected?
- Is the argument the expected type and within an allowed set?
- Does the player have the required role or permission?
- Is the player close enough to the interaction?
- Does the player own the target vehicle or item?
- Can the action be repeated too quickly?
- What happens if the database or inventory operation fails?
Do not rely on a client distance check, disabled button or hidden menu as the access-control layer.
NUI callbacks are not a security boundary
NUI callbacks connect the browser and the resource. They are useful for UI communication, but a callback receiving { action = "open" } does not prove that the player is allowed to perform that action. Validate permissions and game state again before changing anything.
Always return a callback response so the UI does not stall. For sensitive actions, return a clear success or error result and render that state instead of pretending that the click succeeded.
State bags: useful, but understand the write rules
State bags synchronise arbitrary state associated with players and entities. They are useful for sharing display state such as a stress value or a vehicle flag, but they are not a reason to trust arbitrary client writes. Cfx.re documents that player state can be written by the player and server by default, with stricter server-only behaviour available through sv_stateBagStrictMode.
Use a state bag for the data model you actually intend to replicate. For security-sensitive values, decide who can write it and validate changes on the server. Read the state bag documentation.
Rate-limit repeatable requests
Even a valid action can be abused by being called hundreds of times. Add a server-side cooldown where an event can trigger work, charge money, spawn an entity, write to a database or send notifications.
Keep cooldowns per player and clean them on playerDropped. Do not use a client timestamp as the authoritative clock.
Apply this to HUD resources
A HUD normally reads client state and displays it. That is safe when it remains display-only. The risk appears when the UI can:
- change admin layout or permissions;
- mutate stress, money or inventory;
- control a vehicle or ownership record;
- trigger a server-side database write;
- spawn or transfer an entity.
Zloma HUD keeps normal display updates in the client/NUI path while sensitive admin and stress/inventory paths require server-side handling. The same design rule should apply to any server resource, whether the UI is React, Vue or plain HTML.
Security review checklist
Before releasing a resource:
- list every networked event and callback;
- mark which ones can be reached by clients;
- identify the sensitive values each one affects;
- move authoritative reads to the server;
- add type, permission, ownership, distance and rate checks;
- test malformed and repeated requests;
- log useful failures without exposing secrets.
Final takeaway
The client makes requests. The server decides whether those requests are valid. Build that rule into the resource architecture from the beginning and the UI becomes easier to trust, test and maintain.
Keep learning
How to Build a FiveM NUI HUD with Lua and React
Learn the core architecture behind a FiveM NUI HUD: fxmanifest setup, Lua-to-browser messages, NUI callbacks, responsive layout and practical debugging.
FiveM PerformanceFiveM HUD Performance: Update Loops, NUI Messages and Optimization
Learn how to optimize a FiveM HUD with sensible update intervals, change-only NUI messages, cached values and practical client performance testing.
FiveM ResourcesWhat Makes a Legit FiveM Script Store? A Buyer’s Checklist
Learn how to evaluate a FiveM script store for licensing, documentation, compatibility, security, support and long-term maintenance before you buy.
Build a better server experience
Need a production-ready FiveM resource?
Explore Zloma Scripts for practical resources with clean interfaces, framework compatibility, and documentation you can actually use.
Browse Zloma Scripts