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:

lua
local locked = Entity(vehicle).state.locked

Writing from the server

On the server, setting a value replicates it to the clients that can see that bag:

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

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

lua
-- client
LocalPlayer.state:set('inService', true, true)  -- replicated to the server
LocalPlayer.state.hasMask = true                -- local only

The 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.

lua
-- client
local weather = GlobalState.weather

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

lua
-- 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)
  • keyFilter is the key you care about, or nil for every key.
  • bagFilter limits the bag, for example a specific player bag, or nil for all.
  • bagName looks like entity:1234, player:5 or global. GetEntityFromStateBagName turns an entity bag into a handle, and GetPlayerFromStateBagName does 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 GlobalState stays until you clear it, so set it back to nil when you are done.
lua
Player(source).state.cuffed = nil

Checklist

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 →

Keep reading