Commands and key mappings
There are three ways to bind an action on a QBCore server, and they solve different problems. Using
the wrong one is why keybinds end up hardcoded, unrebindable, or executable by anyone. The three are
RegisterCommand, RegisterKeyMapping, and QBCore.Commands.Add. Each section below covers one,
and Choosing between them is the summary table.
Primary sources
REGISTER_KEY_MAPPING,citizenfx/fivemat3926a49.REGISTER_COMMAND,citizenfx/fivem, read 2026-09-11.server/commands.luainqbcore-fivem/qb-coreat9b3cddc.
RegisterCommand
void REGISTER_COMMAND(char* commandName, func handler, BOOL restricted);| Parameter | Meaning |
|---|---|
commandName | The command name, without a leading slash |
handler | Called as handler(source, args, rawCommand) |
restricted | When true on a server command, requires the command.<name> ace |
The native documentation is explicit that restricted is not used on the client side.
Permissions can only be checked on the server, so a command that needs to be restricted has to be
registered in a server script.
On the server, source is the player’s server ID, or 0 when the command came from the server
console, an RCON client, or another resource.
-- server side
RegisterCommand('ping', function(source, args, rawCommand)
if source > 0 then
-- a player ran it
else
-- console, RCON, or a resource
end
end, false)RegisterKeyMapping
void REGISTER_KEY_MAPPING(char* commandString, char* description, char* defaultMapper, char* defaultParameter);| Parameter | Meaning |
|---|---|
commandString | The command to execute. This is also the identifier of the binding |
description | The label shown in the FiveM settings menu |
defaultMapper | The mapper ID for the default binding, for example keyboard |
defaultParameter | The IO parameter ID, for example f3 |
The point of this native is that the default is only a default. Once the mapping is registered, the
player can rebind it from the FiveM settings menu, and their choice survives restarts. A key handled
by polling IsControlJustPressed in a loop cannot be rebound and does not appear in that menu.
Registering an alternate key for the same command uses the ~! prefix on the command string.
-- client side
RegisterCommand('openinventory', function()
TriggerEvent('myresource:client:openInventory')
end, false)
RegisterKeyMapping('openinventory', 'Open inventory', 'keyboard', 'TAB')
RegisterKeyMapping('~!openinventory', 'Open inventory, alternate key', 'keyboard', 'i')Hold to act, the plus and minus pair
For an action that should last only while the key is held, register two commands whose names differ
only by a leading + and -, and map the + one. The runtime invokes +name on key down and
-name on key up.
-- client side
local handsUp = false
RegisterCommand('+handsup', function()
if handsUp then return end
handsUp = true
CreateThread(function()
while handsUp do
TaskHandsUp(PlayerPedId(), 250, PlayerPedId(), -1, true)
Wait(200)
end
ClearPedTasks(PlayerPedId())
end)
end, false)
RegisterCommand('-handsup', function() handsUp = false end, false)
RegisterKeyMapping('+handsup', 'Hands Up', 'keyboard', 'i')The thread is created on key down and ends on key up, so nothing runs while the key is untouched.
The wait is shorter than the task duration passed to TaskHandsUp, which keeps the animation from
lapsing between refreshes. A permanent while true do Wait(0) end thread would do the same job at
one check per frame for the lifetime of the resource.
qb-scoreboard uses the same + and - pair. With
Config.Toggle = true it registers a single scoreboard command that flips state. With
Config.Toggle = false it registers +scoreboard and -scoreboard and maps the + one, so the
panel is only visible while the key is held.
Rules that trip people up
- Register the command first.
RegisterKeyMappingbinds a key to a command string. If no command by that name exists, pressing the key does nothing. - Both calls belong in a client script for a key the player presses, because the key press happens on the client.
- The mapping is registered per resource. Restarting the resource re-registers it. Renaming the command creates a new binding and the player’s old rebind is orphaned.
- The description is what the player sees. Prefix it with your server or resource name if you register several, because the settings list is long.
- Do not poll for a key you have mapped. Pick one mechanism.
- A
+command registered without its-partner leaves the state stuck on after the key is released.
QBCore.Commands.Add
QBCore.Commands.Add(name, help, arguments, argsrequired, callback, permission, ...)This is the server-side wrapper around RegisterCommand. It adds ace permission registration, a
cancellable QBCore:Server:PreCommandExecution event, an argument count check, and a registry at
QBCore.Commands.List. The parameter table and what each of those does are on
Commands reference. What matters when choosing it over the
other two mechanisms is that it is a chat command with a permission level, not a key.
-- server side
QBCore.Commands.Add('setjob', 'Set a player job', {
{ name = 'id', help = 'Player ID' },
{ name = 'job', help = 'Job name' },
{ name = 'grade', help = 'Job grade' },
}, true, function(source, args)
-- args are strings, convert and validate before use
local target = tonumber(args[1])
if not target then return end
end, 'admin')Because it defaults to 'user', omitting the permission produces an unrestricted command. If the
command does anything privileged, either pass a permission level or check inside the callback.
qb-towjob takes the second route: its /tow command is registered at
the default level and the handler checks the caller’s job.
args always arrive as strings. Convert and range check them yourself. Several upstream resources
call tonumber(args[n]) and use the result without a nil check, which raises a server-side Lua error
on bad input rather than a helpful message.
Choosing between them
| Goal | Use |
|---|---|
| A rebindable key for a client action | RegisterCommand plus RegisterKeyMapping, both client side |
| A hold-to-act key | +name and -name commands, map the + one |
| A chat command anyone can use | QBCore.Commands.Add with the default permission |
| A chat command for staff | QBCore.Commands.Add with 'admin' or your own level |
| A console or RCON command | RegisterCommand server side |
| An action triggered by proximity rather than a key | A target or zone resource, see qb-target |
Related pages
- Admin commands for the command list a stock QBCore server ships with, and how permission levels are assigned
- Commands reference for the stock command list itself, rather than how to register your own
- Server functions for
AddPermission,HasPermission, andGetPermission