OneSync, scope, and networked entities
Most “it works for me but not for other players” bugs on a QBCore server trace back to one misunderstanding: what the client can see is not what the server can see, and neither is a complete picture of the world.
This guide covers the model. The behaviour described here comes from the Cfx.re runtime, not from QBCore, so it applies to every resource you run.
Primary sources
The statements on this page are drawn from Cfx.re documentation read at pinned commits:
- OneSync ,
citizenfx/fivem-docsatce72ccb. - Network and local IDs ,
citizenfx/fivem-docsat1b1b131. ADD_STATE_BAG_CHANGE_HANDLER,citizenfx/fivem, read 2026-09-11, for the bag IDs and the handler signature.
Check the live pages at docs.fivem.net before relying on a detail. Older forum and tutorial material about OneSync convars and culling natives is frequently out of date, and the culling natives in particular are documented as deprecated with known unfixable issues.
Client scope is a bubble, not the world
Under OneSync Infinity the server does not create every entity on every client. Each player has a focus zone around them, and entities outside it are not created locally at all. The Cfx.re documentation gives that zone as a hardcoded 424 units. Player culling works the same way: players outside the zone are not created, or are deleted locally.
The consequence is stated directly in that documentation: all player iteration has to happen server side.
That single sentence invalidates a large amount of community FiveM code. Any client-side loop over players is a loop over the players this client happens to have loaded right now.
What this breaks in practice
| Client-side approach | Why it fails |
|---|---|
GetActivePlayers() to count players online | Returns only players inside the local focus zone |
| A client loop to find the nearest police officer | Officers further away simply do not exist locally |
GetGamePool('CVehicle') to find a specific vehicle | Only contains vehicles created on this client |
| Checking on the client whether a target is in range before granting a reward | The client’s view is both incomplete and forgeable |
The QBCore client helpers are built on exactly those primitives.
QBCore.Functions.GetPlayers() is a direct alias for GetActivePlayers(), and
GetClosestVehicle and GetClosestObject scan GetGamePool. See
Client functions. They are fine for local presentation. They are not a
source of truth.
Do the check on the server instead, where QBCore.Functions.GetQBPlayers() and
GetEntityCoords(GetPlayerPed(src)) reflect the whole server. See
Server functions, and
qb-vineyard for a resource that does this correctly throughout.
Scope events
The server can observe scope changes through the playerEnteredScope and playerLeftScope events:
-- server side
AddEventHandler('playerEnteredScope', function(data)
local playerEntered, player = data['player'], data['for']
print(('%s entered %s scope'):format(playerEntered, player))
end)The Cfx.re documentation attaches a performance warning to both. They fire once per pair, so with 32
players inside one player’s scope the handler runs 32 times for that player alone. The documented
recommendation is to use state bags with ADD_STATE_BAG_CHANGE_HANDLER whenever you need scoped
notifications.
Entity handles are not network IDs
This is the second recurring source of bugs, and the Cfx.re “Network and local IDs” page is unambiguous about it.
| Concept | Scope | Stable across machines |
|---|---|---|
| Entity handle | Local to one client | No |
| Network ID | 16-bit, shared | Yes, for the lifetime of the entity |
| Player index | Local to one client, 0 to 128 | No |
Server ID, the source | Server-wide | Yes, for the session |
An entity handle refers to the game’s internal representation on that machine. The same entity will have different handles on different clients. Handles are not typically reused for different entities, but the same entity recreated locally or remotely may get a new one.
A network ID is the 16-bit identifier that does round-trip. It stays constant for the life of the entity, though the IDs themselves are reused after an entity is gone.
The rule
Never send an entity handle across the client and server boundary. Send a network ID.
-- client side, wrong
TriggerServerEvent('myresource:server:doThing', vehicle)
-- client side, right
TriggerServerEvent('myresource:server:doThing', NetworkGetNetworkIdFromEntity(vehicle))-- server side
RegisterNetEvent('myresource:server:doThing', function(netId)
local entity = NetworkGetEntityFromNetworkId(netId)
if not DoesEntityExist(entity) then return end
-- now verify the caller is actually allowed to act on it
end)qb-towjob does this correctly: its qb-tow:server:nano event takes a
network ID, resolves the entity server side, and then measures the distance itself.
A network ID is not guaranteed to be in scope on a given client either. Verify with
NETWORK_DOES_ENTITY_EXIST_WITH_NETWORK_ID before using one you received.
Players
The same split applies to players. The server ID, seen as source in server scripts, is the stable
identifier. The client-side player index is local and does not round-trip. Convert on the client
with GetPlayerFromServerId and GetPlayerServerId. GetPlayerFromServerId returns -1 when that
player is not currently created on this client, which under Infinity is common.
Server-created entities
OneSync lets the server create peds, vehicles, and objects directly. That is the preferred approach for anything the server owns, because a client cannot refuse to create it and cannot delete it casually.
-- server side
local vehicle = CreateVehicleServerSetter(joaat('blista'), 'automobile', 2204.795, -887.9213, 1461.224, 90.0)
SetEntityOrphanMode(vehicle, 2)SetEntityOrphanMode with the KeepEntity flag guarantees the server will not delete the entity.
Per the Cfx.re documentation, a client can still request its deletion, so this is a persistence
guarantee against server-side cleanup, not a hard lock.
RPC natives
Some natives called on the server are actually executed on a client, typically whichever client owns the entity. The Cfx.re documentation calls these RPC natives and states plainly that these calls are fallible and not guaranteed to run.
This matters for QBCore specifically. The upstream comment above
QBCore.Functions.SpawnVehicle on the server notes that the CreateVehicle RPC still uses a client
for creation, so a player must be nearby. If you call it with nobody in the area, nothing is created
and you get no error.
If you need a guarantee, use CreateVehicleServerSetter and the equivalent server-setter natives
rather than the RPC path.
Entity lockdown and routing buckets
A routing bucket is the closest thing FiveM has to a dimension or virtual world. Players and entities only see others in the same bucket, and each bucket gets its own world grid for population ownership.
Buckets also carry a lockdown mode, which is what makes them a security boundary:
| Mode | Meaning |
|---|---|
full | Disables dummy object creation. Restricted to one specific FiveM GTA V build, named in the Cfx.re table linked above |
strict | No entities can be created by clients at all |
relaxed | Only script-owned entities created by clients are blocked |
inactive | Clients can create any entity they want |
-- server side
SetRoutingBucketEntityLockdownMode(1, 'strict')
SetRoutingBucketPopulationEnabled(1, false)
SetPlayerRoutingBucket(source, 1)
SetEntityRoutingBucket(vehicle, 1)The documented use cases are multi-mode servers, session and party systems, and instancing a character screen away from live gameplay.
The Cfx.re documentation also states explicitly what buckets are not for: interiors. It directs you to the traditional conceal natives, or to future 3D-scoped routing policy, for instanced zones. That is worth taking seriously before you reach for a bucket to separate apartments.
QBCore wraps the player and entity natives in SetPlayerBucket and SetEntityBucket, which also
maintain their own registries and write an instance state bag key. The lockdown and population
natives have no core wrapper, so call them directly. See
Server functions for the registry details and their cleanup behaviour.
State bags instead of polling
Where you would otherwise loop on the client to watch something, a state bag replicates a value and lets both sides subscribe to changes. The Cfx.re scope documentation recommends them specifically in place of the scope events.
QBCore already uses one: SetPlayerBucket sets instance on the player’s state bag with
replication enabled, so a client can read LocalPlayer.state.instance without asking the server.
Subscribing costs nothing while the value does not change:
-- client side, replaces a loop that polled LocalPlayer.state.instance
AddStateBagChangeHandler('instance', nil, function(bagName, key, value)
if bagName ~= ('player:%s'):format(GetPlayerServerId(PlayerId())) then return end
print(('moved to bucket %s'):format(value))
end)The second argument is a bag filter such as entity:65535, and nil means no filter, which is why
the handler still has to check whose bag it received. The native documentation gives the bag IDs as
player:Source, entity:NetID, and localEntity:Handle, and the handler receives
(bagName, key, value, reserved, replicated).
A checklist for your own resources
These are the scope-specific rules from this page. For the wider review of what a server handler must validate before it grants anything, use the server-authority checklist.
- Decide what is presentation and what is authority. Client code does presentation.
- Cross the boundary with network IDs and server IDs, never handles or player indices.
- Verify an entity exists on the receiving side before acting on it.
- Prefer server-setter natives over RPC natives when the entity must exist.
- Use state bags rather than polling or scope events for replicated values.
- Treat a routing bucket as a visibility and creation boundary, and read the Cfx.re guidance before using one for interiors.
Related pages
- Client functions
- Server functions
- Commands and key mappings
- qb-vineyard, a server-authoritative job implementation
- qb-towjob, which passes a network ID across the boundary