Events: how your code talks
So far your code has run on its own, top to bottom. A real FiveM server is different: lots of separate scripts run at the same time, and the player's game and the server are two separate programs that cannot read each other's memory. So how do they talk? They send each other messages. Those messages are called events, and they are the single most used idea in all of FiveM. By the end of this lesson you will have sent one yourself and watched it land.
What an event is
An event is a named message your code can send and listen for. That is the whole idea. One piece of code announces "this thing happened" by sending the message, and any other piece of code that cares has said "tell me when that message arrives" and reacts to it.
Two words do all the work. You trigger an event to send it, and you write a handler to listen for it. The handler is just a block of code that runs whenever the matching message arrives, the same way the body of a command runs when you type the command.
Events have names, and the names are plain strings you choose, like qu_hello:greet. The colon in the middle is not special to FiveM. It is a naming habit: resourceName:action. Putting your resource name in front stops a clash the day another resource also wants an event called greet. Two sides simply have to agree on the exact same name, character for character, or the message goes nowhere.
Vocabulary
- event
- A named message your code can send and listen for. The name is a plain string you choose, like qu_hello:greet.
- trigger
- To send an event, so that every handler listening for that name runs. You trigger with TriggerEvent, TriggerServerEvent, or TriggerClientEvent.
- handler
- A block of code that runs when its event arrives. You attach one with AddEventHandler, or with RegisterNetEvent for events that cross the network.
- RegisterNetEvent
- The function that marks an event name as allowed to arrive from the other side of the network. Required on the receiving side for any event that crosses between client and server.
- source
- On the server, the id of the player who sent the event. The server reads it to know who asked, instead of trusting the player to say who they are.
Why events exist
Remember the mental model from the client versus server lesson: the player's game (the client) and the server are two separate programs running on two different machines. They do not share variables. The client cannot reach into the server and read a value, and the server cannot reach into the client. On top of that, even on one machine, every resource is its own walled-off sandbox and cannot read another resource's variables either.
So if these separate parts cannot share variables, how do they cooperate at all? They send events. An event is the seam between two parts that otherwise cannot touch. One side announces a fact, the other side reacts, and neither has to reach inside the other. That is why nearly everything in FiveM, buying an item, healing a player, opening a menu, runs on events. It is the only clean way for separate parts to talk.
The three you will use
There are three trigger functions you will reach for constantly. They differ in one thing only: which direction the message travels.
TriggerEvent: same side
TriggerEvent fires an event on the same side you are already on. Client code triggers it, only client handlers hear it. Server code triggers it, only server handlers hear it. It never crosses the network. You listen for it with AddEventHandler.
AddEventHandler('qu_hello:greet', function(name)
print('[qu_hello] greet handler heard ' .. name)
end)
TriggerEvent('qu_hello:greet', 'world')Read it top to bottom. AddEventHandler says "when an event named qu_hello:greet fires, run this function". The function takes one value, name. Then TriggerEvent fires that exact name and hands it the value 'world', which arrives as name. The handler runs and prints [qu_hello] greet handler heard world. Same side, no network involved.
TriggerServerEvent: client to server
TriggerServerEvent is the client saying "fire this event over there on the server". The message leaves the player's machine and crosses the network to the server. This is how a player's click reaches your server logic.
The picture above is the shape to memorize. The client triggers, the value crosses the wall, and the server is where you check it. On the server side you must listen with RegisterNetEvent, not AddEventHandler, because the event arrives from across the network:
RegisterNetEvent('qu_bank:deposit', function(amount)
local src = source
print('[qu_bank] player ' .. src .. ' wants to deposit ' .. tostring(amount))
end)RegisterNetEvent marks the name as allowed to arrive from the other side and attaches the handler in one call. The first line, local src = source, grabs the id of the player who sent the event. The server reads who the player is from source. It never asks the player to say who they are.
TriggerClientEvent: server to client
TriggerClientEvent is the reverse trip: the server sending an event back to a client. It mirrors TriggerServerEvent with one extra piece in second place, the target, which says who should receive it.
TriggerClientEvent('qu_bank:balance', src, 1500)Here the server sends qu_bank:balance to one specific player, the one whose id is in src, and hands them the value 1500. To send to every connected player at once, you pass -1 as the target instead of one id. On the client side you again listen with RegisterNetEvent, because the message arrives from across the network.
Build a tiny event
Time to send a real one. You will register a net event on the server that prints a message, then trigger it from a command on the client. This proves a value crossing from the client to the server, which is the move underneath almost every feature you will ever build.
Add a server handler
Open server.lua inside your qu_hello resource and add this:
RegisterNetEvent('qu_hello:ping', function()
local src = source
print('[qu_hello] ping from player ' .. src)
end)RegisterNetEvent opens the gate so this event is allowed to arrive from a client. The handler grabs source into src (always the first line on a server handler) and prints who sent it.
Add a client command that fires it
Open client.lua in the same resource and add this:
RegisterCommand('ping', function()
TriggerServerEvent('qu_hello:ping')
end, false)RegisterCommand wires the word ping to code, exactly like in earlier lessons. When you type it, TriggerServerEvent fires the matching event name across the network to the server. The false lets you run it from your own game.
Reload the resource
In the txAdmin Live Console, type this and press Enter:
restart qu_helloFire the command and read the server console
Join the server, press F8 to open the client console, type ping, and press Enter. Then look at the server console (the txAdmin Live Console), not F8, because the print lives in server code.
The number is your own server id, so it may not be exactly 1, but a player id will appear. You just sent a message from your game, across the network, to the server, and the server told you who it came from. That round trip is the backbone of FiveM.
The safety rule
Here is the rule that protects your server, and it is the most important sentence in this lesson: never trust event data from a client. The server must check it before acting on it.
The reason is simple and a little scary. A normal player triggers your event with sensible values. But a player running a cheat menu can open their console and fire any event you registered with RegisterNetEvent, with any values they invent. Your RegisterNetEvent is effectively a public button anyone on your server can press, with any payload they like.
You will learn the full validation checklist in the paid Tracks. For now, lock in the instinct: data from a client is a request, not a fact. The server is the judge.
Common beginner mistakes
| Symptom | Fix |
|---|---|
The client fires the event but the server handler never runs. | The receiving side used AddEventHandler instead of RegisterNetEvent. A net event arriving from across the network is rejected unless its name was registered with RegisterNetEvent on the side that receives it. Switch the server handler to RegisterNetEvent. |
The server happily gives a player whatever amount they sent. | You trusted client data. A player can fire the event with any value. The server must check the type and range, and read identity from source, before acting. Never act on a raw client value. |
Nothing happens at all, no print, no error. | The event name does not match on both sides. A single typo, a missing colon, or a capital letter difference means the message you trigger matches no handler. Compare the two strings character by character. |
A client triggers a server event saying 'give me $1,000,000'. Why must the server not just believe it?
Because the value came from the client, and the client cannot be trusted. A normal player would send a sensible amount, but a player running a cheat menu can fire that same event with any number they type. The server's handler runs with the server's full authority, so whatever it does, it does for real. If the server believes the client and adds the money, the cheater is now a millionaire. The server has to decide for itself: read who the player is from source, check the amount is a number in a sane range, and confirm the player is actually allowed to receive it, before it changes anything. The client makes a request. The server makes the decision.
Try it yourself
What you can do now
- Explain that an event is a named message you trigger to send and write a handler to listen for.
- Say why events exist: the client and server cannot share variables, so they send messages instead.
- Pick the right trigger for the direction: TriggerEvent for the same side, TriggerServerEvent for client to server, TriggerClientEvent for server to client.
- Use RegisterNetEvent (not AddEventHandler) on the receiving side of any event that crosses the network.
- State the safety rule: never trust client data; the server must validate it and read identity from source.
You can now make separate parts of a server talk to each other, which is most of what real features are made of. The remaining piece is the simplest way to set an event off on purpose: a command you type. Next up: "Commands: trigger code on demand".