Skip to Content
AdvancedOneSync, Scope, and Networked Entities

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:

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 approachWhy it fails
GetActivePlayers() to count players onlineReturns only players inside the local focus zone
A client loop to find the nearest police officerOfficers further away simply do not exist locally
GetGamePool('CVehicle') to find a specific vehicleOnly contains vehicles created on this client
Checking on the client whether a target is in range before granting a rewardThe 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.

ConceptScopeStable across machines
Entity handleLocal to one clientNo
Network ID16-bit, sharedYes, for the lifetime of the entity
Player indexLocal to one client, 0 to 128No
Server ID, the sourceServer-wideYes, 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:

ModeMeaning
fullDisables dummy object creation. Restricted to one specific FiveM GTA V build, named in the Cfx.re table linked above
strictNo entities can be created by clients at all
relaxedOnly script-owned entities created by clients are blocked
inactiveClients 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.

  1. Decide what is presentation and what is authority. Client code does presentation.
  2. Cross the boundary with network IDs and server IDs, never handles or player indices.
  3. Verify an entity exists on the receiving side before acting on it.
  4. Prefer server-setter natives over RPC natives when the entity must exist.
  5. Use state bags rather than polling or scope events for replicated values.
  6. Treat a routing bucket as a visibility and creation boundary, and read the Cfx.re guidance before using one for interiors.