Migrating from Deprecated APIs
FiveM’s scripting APIs evolve, and some older patterns still run but no longer match the current recommended approach. This page covers two common ones: chat message events and command registration. It is not about framework migration — see Framework Migration for moving between ESX, QBCore, and Qbox.
Chat messages: chatMessage event to chat:addMessage
Older resources trigger the client-side chatMessage event directly, or
call TriggerClientEvent('chatMessage', ...) with positional arguments for
author, color, and text. The built-in chat resource now expects a
structured message object sent through chat:addMessage, which supports a
template, per-channel color, multiline text, and named args instead of
hand-formatted strings.
Before
-- Deprecated: positional args, no structure, easy to get the order wrong
TriggerClientEvent('chatMessage', playerSource, '', { 255, 0, 0 }, '^1Server: ^0' .. message)-- Deprecated: raw chatMessage broadcast to everyone
TriggerClientEvent('chatMessage', -1, 'Server', { 255, 0, 0 }, message)After
TriggerClientEvent('chat:addMessage', playerSource, {
color = { 255, 0, 0 },
multiline = true,
args = { 'Server', message }
})-- Broadcast to everyone
TriggerClientEvent('chat:addMessage', -1, {
template = '<div class="chat-message server"><b>{0}</b><br>{1}</div>',
args = { 'Server', message }
})args values are inserted positionally into the default template (or your
custom template), and are HTML-escaped by the chat resource — you don’t
need to manually strip player-supplied text before sending it as an arg.
Keep building the message object on the server so a modified client can’t
forge the sender name or color.
Command registration: unrestricted RegisterCommand to ACE-aware restriction
Older command code registers a command and either performs no permission
check at all, or only hides the command from suggestions on the client — which
does not stop a modified client or a direct console call from executing it.
RegisterCommand takes a third restricted argument that ties the command to
ACE permissions and is enforced by the game itself before your handler runs.
Before
-- Deprecated: no restriction argument, anyone can run it
RegisterCommand('kick', function(source, args)
local target = tonumber(args[1])
if target then
DropPlayer(target, 'Kicked by staff')
end
end)
-- Deprecated: hiding the command client-side is not access control
TriggerEvent('chat:addSuggestion', '/kick', 'Kick a player (staff only)')Hiding a command from chat:addSuggestion only removes it from the
autocomplete list. The command is still fully callable by typing it directly
or invoking it through the client console, so it is not a security boundary
on its own.
After
RegisterCommand('kick', function(source, args)
local target = tonumber(args[1])
if not target then
return
end
local targetPed = GetPlayerPed(target)
if targetPed == 0 then
return
end
DropPlayer(target, 'Kicked by staff')
end, true) -- restricted = true
-- Suggestion is still useful for discoverability, but is not the access check
TriggerEvent('chat:addSuggestion', '/kick', 'Kick a player (staff only)')With restricted set to true, the game checks the calling player’s ACE
permissions for the command.kick object before the handler runs at all, so
an unauthorized call never reaches your Lua code. Grant that permission
through your server’s server.cfg — see Permissions
for the full ACE model:
add_ace group.admin command.kick allow
add_principal identifier.license:yourlicensehash group.adminPair the restricted flag with server-side validation of the arguments
themselves (target exists, target is not already offline, and so on) — ACE
restriction controls who can call the command, not whether the arguments it
receives are valid.