Keybinds#
Scoreboard registers no chat commands. It is opened with a single key, registered through ox_lib:
Keybind | Default | Listed in game as |
|---|---|---|
|
| Press key to toggle the ScoreBoard |
Because it is a FiveM key mapping, every player can rebind it under Settings → Key Bindings → FiveM, and their choice is remembered. That is also why changing Config.ScoreBoardKey later only reaches players who have never joined: everyone else keeps the key they already have.
A fixed key instead#
For a key players cannot change, switch the mapping off and listen to a game control directly:
Config.ModifyKey = true -- ignore the key mapping
Config.NewKeyNumber = 38 -- the control that opens it (38 = E)
Config.NewKey = "e" -- the key that closes it againConfig.NewKeyNumber is a control index from the FiveM controls list. Config.NewKey is the same key as the interface sees it — a lowercase letter such as "e", or a name such as "Tab" — and must match, or only Escape will close the window.
A key mapping costs nothing while idle; a fixed control is checked every frame. Prefer the default unless you need the key locked.
Exports Your Job Script Provides#
Scoreboard has no exports of its own to call. It works the other way round: it calls two server-side exports on your job scripts, whenever Config.DisplayedJobs points at them — see Integrations.
getOnDutyCount
Returns how many players are on shift for this job. Called with no arguments each time anyone opens the scoreboard. The number goes on the job's Active Jobs card.
exports('getOnDutyCount', function()
local count = 0
for _, onDuty in pairs(OnDuty) do
if onDuty then count = count + 1 end
end
return count -- number
end)isPlayerOnDuty
Returns whether one player is on shift. Called once for every player with this job each time the scoreboard opens. The player's server ID arrives as a string; true shows On Duty, anything else Off.
Parameter | Type | Description |
|---|---|---|
| string | The player's server ID. |
exports('isPlayerOnDuty', function(src)
return OnDuty[tostring(src)] == true -- boolean
end)Both calls are protected: an error, a stopped resource or a missing export counts as 0 or Off, and the scoreboard keeps working. Several Quasar jobs — qs-minerjob, qs-hunter and qs-lumberjack among them — already ship both, so pointing their entries at them is all it takes.
A Complete Example#
A job script that tracks its own shift needs a table, an event to clock in and out, a clean-up when a player leaves, and the two exports.
Server side#
local OnDuty = {}
RegisterNetEvent('my-job:setDutyStatus', function(onDuty)
OnDuty[tostring(source)] = onDuty == true
end)
AddEventHandler('playerDropped', function()
OnDuty[tostring(source)] = nil
end)
exports('getOnDutyCount', function()
local count = 0
for _, onDuty in pairs(OnDuty) do
if onDuty then count = count + 1 end
end
return count
end)
exports('isPlayerOnDuty', function(src)
return OnDuty[tostring(src)] == true
end)Clearing the entry on playerDropped matters — without it, a player who disconnects on shift keeps being counted until the next restart.
Client side#
TriggerServerEvent('my-job:setDutyStatus', true) -- clock in
TriggerServerEvent('my-job:setDutyStatus', false) -- clock outIn the scoreboard's config#
-- config/config.lua, inside Config.DisplayedJobs
my_job = {
order = 9,
label = "My Job",
icon = "fa-solid fa-briefcase",
onDutyExport = { resource = "my-job", fn = "getOnDutyCount" },
playerDutyExport = { resource = "my-job", fn = "isPlayerOnDuty" },
},The key my_job must be the job's name in your framework. For the badges to show, that name must also be in Config.DisplayablePlayerJobs — the count works without it.
Reading the Player List from Code#
The data behind the window comes from an ox_lib callback, so a client script — a phone app, a tablet, a HUD counter — can read the same snapshot:
local players, online, maxPlayers, myId, police, jobCounts, jobStats =
lib.callback.await('qs-scoreboard:getServerPlayers', false)Value | Contents |
|---|---|
| An array of |
| The number of players in the list. |
| The slot count. |
| The caller's server ID. |
| Online players with a police job. |
| The count for each |
| The caller's |
Each call walks every online player and calls the duty exports, so read it when it is needed — on a key press, or on a timer of several seconds — never every frame.



