FiveM state bags: Entity(entity).state, GlobalState and handlers
How FiveM state bags work: Entity(entity).state, Player(source).state, LocalPlayer.state, GlobalState, :set, change handlers, and when to prefer them to events
You want to share a small piece of data between the server and every client, for example that a vehicle is locked, that a player is handcuffed, or the current weather, and you keep writing events that fire on every change and get out of sync for players who join later. State bags are the built-in answer.
This article covers the state bag types, how to write and read them, how to react to changes, and when an event is still the better choice.
What a state bag is
A state bag is a table-like store of keys and values attached to something. The runtime syncs it for you, including to players who connect after the value was set. FiveM gives you three kinds:
| Bag | Read and write with | Lives on |
|---|---|---|
| An entity | Entity(entity).state |
The entity (ped, vehicle, object) |
| A player | Player(source).state on the server, LocalPlayer.state on the client |
The player |
| The server | GlobalState |
The whole server |
In all of them you read a value like a normal field:
local locked = Entity(vehicle).state.lockedWriting from the server
On the server, setting a value replicates it to the clients that can see that bag:
-- server
local veh = CreateVehicle(`adder`, 215.0, -810.0, 30.7, 90.0, true, true)
Entity(veh).state.locked = true
Player(source).state.cuffed = true
GlobalState.weather = 'RAIN'You can use either the field style or the explicit call, which also lets you choose whether the value is replicated:
Entity(veh).state:set('locked', true, true):set(key, value, replicated) takes the key, the value, and a boolean. With true the value is sent to the other side, with false it stays on the side that wrote it.
Tip: values can be numbers, strings, booleans and tables. A table is copied when you set it, so changing a field in the table you read back does nothing. Set the whole table again to update it.
Writing from the client
A client can write to its own player and to entities it controls, but a plain assignment stays local:
-- client
LocalPlayer.state:set('inService', true, true) -- replicated to the server
LocalPlayer.state.hasMask = true -- local onlyThe server can read Player(source).state.inService after that. Treat it as untrusted: a modified client can write any value it wants. Use client-written state for convenience, such as UI flags, and keep anything with an advantage attached (money, job, permissions) on the server.
GlobalState is the opposite: the server writes it, every client reads it, and clients cannot change it for others.
-- client
local weather = GlobalState.weatherReacting to changes
Instead of polling a value in a loop, register a handler that runs when a key changes. This is the main reason to use state bags on the client:
-- AddStateBagChangeHandler(keyFilter, bagFilter, handler)
AddStateBagChangeHandler('locked', nil, function(bagName, key, value, reserved, replicated)
local entity = GetEntityFromStateBagName(bagName)
if entity == 0 then return end
SetVehicleDoorsLocked(entity, value and 2 or 1)
end)keyFilteris the key you care about, ornilfor every key.bagFilterlimits the bag, for example a specific player bag, ornilfor all.bagNamelooks likeentity:1234,player:5orglobal.GetEntityFromStateBagNameturns an entity bag into a handle, andGetPlayerFromStateBagNamedoes the same for a player bag.
On the client, the entity may not exist yet when the handler fires, for example for a vehicle that is still streaming in. GetEntityFromStateBagName can return 0 in that case, so check for it. Reading the state again when the entity streams in is the safe habit.
The same function exists on the server, where it is handy for reacting to a client writing a replicated value.
State bags or events
| Use a state bag when | Use an event when |
|---|---|
| The value describes the current state of something (locked, cuffed, on duty) | Something happened once (a purchase, a hit, a button press) |
| Players who join later must see the value | Only players present right now need to know |
| Many players need to read it at any time | One player needs to be told something |
| You would otherwise poll in a loop | You need to pass a result or a one-off payload |
A good rule: events are for actions, state bags are for state. If a player joining halfway through would need to be told the same thing again, it is state. See client and server events for the other side.
Common mistakes
- Using it for large or fast-changing data. Every change is sent over the network. Do not write a position into a state bag every frame.
- Trusting client writes. Validate anything a client set before you act on it on the server.
- Expecting the handler to run before the entity exists. Check the handle and read the state again when the entity appears.
- Mutating a table in place. Reading
state.items, adding to it and not setting it again changes nothing. - Forgetting the reset. State on an entity goes away with the entity, but state on a player or
GlobalStatestays until you clear it, so set it back tonilwhen you are done.
Player(source).state.cuffed = nilChecklist
| Symptom | Fix |
|---|---|
| Client sets a value but the server never sees it | Use :set(key, value, true) so it is replicated |
Handler runs but the entity handle is 0 |
The entity is not streamed yet: check, and read the state again later |
| Late joiners miss the value | Use a state bag instead of an event |
| Table change does not sync | Set the whole table again with :set |
Cannot change GlobalState from the client |
Only the server writes it: send an event and let the server set it |
| Old value stays after the job is done | Set the key to nil |
Quick answers
What is a state bag in FiveM?
A key and value store attached to an entity, a player or the whole server. Values you set are synced to the other side by the game, so you do not need to trigger an event for each change.
Can a client change a state bag and have the server see it?
Only if the value is set with replication on, for example LocalPlayer.state:set('key', value, true). A plain assignment on the client stays local, and the server should never trust it for anything that matters.
Do state bags need OneSync?
Entity state bags depend on OneSync, which current servers use by default. Player state and GlobalState are the easier cases to start with.
Scripts that skip this problem
Parrots as PetsFourteen birds that ride your shoulder, fly with you and come back when called.View script →
Advanced BoostingTablet-driven vehicle boosting: contracts from class D to S+, crews and a live queue.View script →
Hookah SystemPlaceable hookahs and lounge furniture with 50+ flavours and effects.View script →