Skip to Content
ScriptingMigrating from Deprecated APIs
Report an error
Open in ChatGPTOpen in Claude

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)

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

chat:addSuggestion is handled only by the client chat script, so calling it with TriggerEvent from a server script does nothing. You rarely need it for restricted commands anyway: when a player joins, the stock server chat sends command suggestions only for commands that player is allowed to run (IsPlayerAceAllowed(player, 'command.<name>') in sv_chat.lua). To add help text, trigger chat:addSuggestion from a client script.

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.admin

Pair 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.