blipsonesyncpolice9 min read · updated 9/19/2026

Live player blips for police and jobs on OneSync

Every police department on a FiveM server wants to see its officers on the map. The first version of that script is always the same: loop over active players, create a blip for each colleague's ped, done. It works in testing with two people standing next to each other and falls apart in production, where officers across the map simply do not appear. The reason is OneSync's scoping, and the fix is to let the server feed positions. This guide builds a job-tracking blip system that works at any distance, stays cheap and does not leak positions to people who should not see them.

Short answer: On OneSync a client only knows about players inside its scope, roughly 424 metres by default, so AddBlipForEntity on a distant colleague's ped fails. Collect positions on the server with GetPlayerPed and GetEntityCoords, send them every one to two seconds only to authorised players, and on the client use an entity blip when the colleague is in scope and a coordinate blip updated with SetBlipCoords when not. Remove blips for players who left or changed job.

FiveM Player Blips on OneSync: Job Tracking That Works

#Why client-only player blips break

With OneSync, the server decides which entities each client knows about. A player's client receives other players and their peds only when they are within its scope, which by default is roughly 424 metres. Outside it, GetPlayerFromServerId(id) returns -1, NetworkIsPlayerActive is false and there is no ped handle to attach a blip to. A client-only script therefore shows colleagues nearby and silently drops everyone else.

Old tutorials written for non-OneSync servers with 32 slots do not mention this, because without OneSync every client knew about every player. On any modern server with OneSync enabled, the server has to supply positions.

#The design

  1. The server keeps a list of which players are on which tracked job, fed by your framework's job events.
  2. Every one to two seconds, the server reads each tracked player's ped position, heading and vehicle state.
  3. It sends each authorised player one compact list of their colleagues.
  4. The client keeps one blip per colleague and updates it, swapping between entity and coordinate blips as the colleague enters or leaves scope.
  5. Blips for colleagues no longer in the list are removed.

This keeps the server as the source of truth, keeps bandwidth tiny and means a cheater cannot see anything their job would not show them anyway.

It also separates two concerns that client-only scripts mix up. Who may see whom is a permission question and belongs on the server, next to your job and duty data. How a colleague looks on the map is a presentation question and belongs on the client, where sprites, colours and categories can change without touching the server loop. Keeping them apart means a department can restyle its blips, or a new division can get its own colour, without anyone reviewing the access logic again.

#Server side

server.lua
local TRACKED = { police = true, ambulance = true }
local members = {}

local function setMember(src, job, callsign)
    if TRACKED[job] then
        members[src] = { job = job, callsign = callsign or GetPlayerName(src) }
    else
        members[src] = nil
    end
end

AddEventHandler('playerDropped', function()
    members[source] = nil
end)

CreateThread(function()
    while true do
        local byJob = {}
        for src, info in pairs(members) do
            local ped = GetPlayerPed(src)
            if ped ~= 0 and DoesEntityExist(ped) then
                local coords = GetEntityCoords(ped)
                local list = byJob[info.job] or {}
                list[#list + 1] = {
                    id = tonumber(src),
                    x = coords.x, y = coords.y, z = coords.z,
                    h = math.floor(GetEntityHeading(ped)),
                    v = GetVehiclePedIsIn(ped, false) ~= 0,
                    n = info.callsign,
                    b = GetPlayerRoutingBucket(src),
                }
                byJob[info.job] = list
            end
        end

        for src, info in pairs(members) do
            local bucket = GetPlayerRoutingBucket(src)
            local visible = {}
            for _, entry in ipairs(byJob[info.job] or {}) do
                if entry.id ~= tonumber(src) and entry.b == bucket then
                    visible[#visible + 1] = entry
                end
            end
            TriggerClientEvent('jobblips:update', src, visible)
        end

        Wait(1500)
    end
end)

Wire setMember to your framework: the job-set and player-loaded events on ESX or QBCore, or your own duty toggle. Tracking on-duty rather than job membership is usually what departments want, since off-duty officers should not appear.

The payload is small. Thirty officers each receiving twenty-nine entries of a few numbers every 1.5 seconds is a few kilobytes per second in total, which is negligible next to normal entity sync.

#Client side

client.lua
local blips = {}

local function styleBlip(blip, entry)
    SetBlipSprite(blip, entry.v and 56 or 1)
    SetBlipColour(blip, 3)
    SetBlipScale(blip, 0.85)
    SetBlipCategory(blip, 7)
    SetBlipPriority(blip, 10)
    ShowHeadingIndicatorOnBlip(blip, true)
    SetBlipAsShortRange(blip, false)
    BeginTextCommandSetBlipName('STRING')
    AddTextComponentSubstringPlayerName(entry.n)
    EndTextCommandSetBlipName(blip)
end

local function removeBlip(id)
    local b = blips[id]
    if b and DoesBlipExist(b.handle) then RemoveBlip(b.handle) end
    blips[id] = nil
end

RegisterNetEvent('jobblips:update', function(list)
    local seen = {}
    for _, entry in ipairs(list) do
        seen[entry.id] = true
        local player = GetPlayerFromServerId(entry.id)
        local ped = player ~= -1 and GetPlayerPed(player) or 0
        local wantEntity = ped ~= 0 and DoesEntityExist(ped)
        local current = blips[entry.id]

        if current and current.entity ~= wantEntity then
            removeBlip(entry.id)
            current = nil
        end

        if not current then
            local handle = wantEntity and AddBlipForEntity(ped)
                or AddBlipForCoord(entry.x, entry.y, entry.z)
            current = { handle = handle, entity = wantEntity }
            blips[entry.id] = current
        end

        if not current.entity then
            SetBlipCoords(current.handle, entry.x, entry.y, entry.z)
            SetBlipRotation(current.handle, entry.h)
        end
        styleBlip(current.handle, entry)
    end

    for id in pairs(blips) do
        if not seen[id] then removeBlip(id) end
    end
end)

AddEventHandler('onResourceStop', function(name)
    if name ~= GetCurrentResourceName() then return end
    for id in pairs(blips) do removeBlip(id) end
end)

Entity blips follow the ped every frame, so nearby colleagues move smoothly. Coordinate blips jump once per update, which is fine for someone half a map away. The heading indicator follows the entity automatically for entity blips; for coordinate blips, the rotation comes from the server heading.

Sprite 56 is the patrol-car icon and 1 is the plain dot, which gives dispatchers an instant read of who is driving. Colour 3 is blue; use 1 for red or 2 for green for EMS. Category 7 lists colleagues under "Other Players" in the legend with their distance, which is surprisingly useful for backup calls. More on categories in blip categories and the legend.

#Hooking it to ESX and QBCore

The server needs to know who is on a tracked job and on duty. Both big frameworks fire server-side events you can listen to instead of polling player data. Check the event names against your framework version, since forks rename them.

server.lua — ESX Legacy
AddEventHandler('esx:setJob', function(src, job)
    if job.onDuty == false then
        setMember(src, nil)
    else
        setMember(src, job.name)
    end
end)

AddEventHandler('esx:playerLoaded', function(src, xPlayer)
    setMember(src, xPlayer.job.name)
end)
server.lua — QBCore
AddEventHandler('QBCore:Server:OnJobUpdate', function(src, job)
    setMember(src, job.onduty and job.name or nil)
end)

AddEventHandler('QBCore:Server:SetDuty', function(src, onDuty)
    local player = exports['qb-core']:GetCoreObject().Functions.GetPlayer(src)
    if not player then return end
    local job = player.PlayerData.job
    setMember(src, onDuty and job.name or nil, player.PlayerData.metadata.callsign)
end)

Also populate the list when the resource starts, by looping over the players already online, so a restart of the blip resource does not leave everyone invisible until they change job.

#Scaling to large servers

The loop above does work proportional to tracked players times recipients. At 300 players with 60 officers and 30 medics that is still only a few thousand small table entries every 1.5 seconds, well within what a server thread handles without a measurable tick. The things that actually cause problems at scale are different:

  • Sending the list to every player and letting the client filter. That multiplies traffic by the player count and leaks positions.
  • Serialising large per-player objects (full player data, inventories) instead of a few numbers.
  • Updating every 100 ms because the blips looked jumpy. Jumpiness only affects out-of-scope blips, which are far away and do not need precision.
  • Recreating blips on every update instead of moving them. Creation is more expensive and makes blips flicker in the legend.

If you need finer updates for a pursuit, raise the rate only for the units involved and only while the pursuit runs. Dispatch-heavy servers sometimes move this whole system into their MDT resource so that one resource owns units, calls and blips together.

#Access control and privacy

  • Decide on the server who receives what. Never send the full list to every client and filter on the client; a modified client ignores your filter.
  • Remove players from members the moment they go off duty or change job, not only on disconnect.
  • Keep routing buckets separate so players in an apartment instance or a different game mode are not shown.
  • For gang or criminal factions, consider showing members only within a radius or only while a territory event is running; permanent enemy-proof tracking changes gameplay a lot.
  • Do not expose other jobs' positions through the same event name with a job parameter the client can choose.

#Useful extras

FeatureHow
Panic buttonFlash the officer's blip with SetBlipFlashes and SetBlipFlashTimer, and set its colour to red
Unit colours by divisionSend a division field and map it to blip colours
Hide while in the pause mapNothing needed; blips render in both views
Show only on the pause mapSetBlipDisplay(blip, 3) for dispatch-only views
Route to a colleagueSetBlipRoute(handle, true); entity blips keep the route updated as they move

Routing to a unit is covered in more depth in GPS routes and waypoints.

#Troubleshooting

SymptomCause
Colleagues only show when closeClient-only script using entity blips, no server feed
Blips duplicate over timeBlip handle not removed when swapping entity and coordinate modes
Blip stuck at an old positionPlayer dropped or went off duty but was not removed from the list
Blip shows your own position twiceServer did not exclude the recipient from the list
Players in apartments visible on the streetRouting bucket not compared

Questions

Why can't I see police blips across the map?
On OneSync your client only knows about players in its scope, about 424 metres by default. Distant players must be sent to the client by the server as coordinates.
How often should player blips update?
Every one to two seconds is enough. Nearby players use entity blips, which move smoothly without extra updates.
Is it safe to send all positions to every client?
No. A modified client can read everything it receives. Filter on the server so each player only gets the positions their job allows.
Can I use state bags for player positions?
Entity state bags only reach clients that have the entity in scope, and rewriting coordinates into state bags every second is heavier than it needs to be. A filtered, server-sent list is simpler and cheaper.
Which sprite should police blips use?
Sprite 1 for officers on foot and 56 for officers in a vehicle is a common, readable choice, coloured 3 (blue).
Should players in other routing buckets appear?
Usually not. Compare GetPlayerRoutingBucket for the sender and recipient on the server and skip entries from a different bucket, so apartment instances and separate game modes stay hidden.

Ready to pick a map?

Twelve themes on three base map styles, $8 each, instant download.

Relevant to what you just read