Advanced Features
Deep dive into advanced of_chat_theme capabilities.
Server-Side Template Resolution
For templates that need server data, use dont_register: prefix:
Config.Theme['dont_register:job_listing'] = T.build()
:bg('#2c3e50')
:icon(Config.Svgs.announce)
:text('Job Listing - {Framework.Functions.GetJobLabel}', {
bold = true,
color = '#27ae60'
})
:message()
:done()
Send from server:
-- src/server/job/ads.lua
TriggerClientEvent('chat:addMessage', -1, {
template = resolvedHtml, -- Server has already resolved placeholders
args = { "Looking for police officers" }
})
Custom Command Handlers
Client Command
-- config/commands/client.lua
Commands.client("custom", {
requireLoaded = true,
handler = function(ctx)
-- ctx.message = full message text
-- ctx.args = split arguments
-- ctx.author = player character name
-- ctx.range = range from config
if ctx.message == "" then
return Framework.Functions.Notify("Usage: /custom [text]", "error")
end
Commands.sendProximity('cmd:me', {
ctx.author,
ctx.message
}, ctx.range)
end,
})
Server Command
-- config/commands/server.lua
Commands.server("broadcast", {
requireAdmin = true,
handler = function(ctx, source)
-- source = player server ID
-- ctx.message = full message
TriggerClientEvent('chat:addMessage', -1, {
templateId = 'announce',
args = { ctx.message }
})
end,
})
Dynamic Placeholder Creation
Create placeholders that respond to context:
-- config/placeholders.lua
-- Client-side
Client.Custom["Placeholder"]["PlayerDistance"] = function(placeholder, orTable, args)
local ped = PlayerPedId()
local playerCoords = GetEntityCoords(ped)
-- Args can contain data passed from handler
if args and args[1] then
local targetCoords = json.decode(args[1])
local distance = #(playerCoords - targetCoords)
return string.format("%.1f m", distance)
end
return "N/A"
end
-- Server-side (for dont_register templates)
Server.Custom["Placeholder"]["OnlinePlayers"] = function(placeholder, orTable, args)
local players = GetPlayers()
return tostring(#players)
end
-- Usage
Config.Theme['cmd:status'] = T.build()
:tag('STATUS')
:message()
:text(' (Distance: {Placeholder:PlayerDistance})')
:done()
Area-Restricted Channels
Only send messages to players within range:
Config.Channels = {
{
id = 'dispatch',
label = 'Dispatch',
participants = {
read = { { type = 'job', value = 'police' } },
write = { { type = 'job', value = 'police' } },
},
area = true,
areaRadius = 100.0, -- 100 metres
}
}
Receivers only get messages from senders within areaRadius.
Custom Styling with CSS
Modify message appearance with inline styles:
Config.Theme['cmd:fancy'] = T.build()
:bg('linear-gradient(to right, #667eea 0%, #764ba2 100%)')
:tag('FANCY', '#ffffff')
:author({
bold = true,
style = 'text-transform: uppercase; letter-spacing: 1px'
})
:text(': ', {
color = 'rgba(255,255,255,0.5)',
style = 'margin: 0 5px'
})
:message({
style = 'font-style: italic; text-shadow: 2px 2px 4px rgba(0,0,0,0.5)'
})
:done()
Command Context Object
Available properties in command handlers:
Commands.client("example", {
handler = function(ctx)
-- String properties
ctx.message -- Full message after command
ctx.author -- Player character name
ctx.name -- Player CFX username
-- Array
ctx.args -- Split arguments: {"/cmd", "arg1", "arg2", ...}
-- Config values
ctx.range -- From Config.Commands[name].range
ctx.los -- Line-of-sight requirement
ctx.area -- Area-restricted
ctx.areaRadius -- Area radius
-- Helpers
ctx:notify(msg, level) -- Show notification
end,
})
Broadcasting Methods
Proximity Only
Message visible to nearby players:
Commands.sendProximity(templateId, args, range)
Proximity with Line-of-Sight
Must be able to see the sender:
Commands.sendProximityLos(templateId, args, range)
Private Message
Only one player sees it:
Commands.sendPrivate(targetServerId, templateId, args)
Global/All Players
Everyone sees it:
Commands.sendAll(templateId, args)
Channel
Send to channel subscribers only:
TriggerEvent('chat:addMessage', {
templateId = 'my:template',
channel = 'police', -- Only police channel members see
args = { ... }
})
Message Preprocessing
Manipulate messages before display:
Commands.client("filtered", {
handler = function(ctx)
-- Censoring example
local cleaned = ctx.message
:gsub("badword1", "****")
:gsub("badword2", "****")
Commands.sendProximity('cmd:me', {
ctx.author,
cleaned
}, ctx.range)
end,
})
State Management
Persist data across chat interactions:
-- Global state table
local PlayerStates = {}
Commands.client("remember", {
handler = function(ctx)
local sid = GetPlayerServerId(PlayerId())
PlayerStates[sid] = {
lastMessage = ctx.message,
timestamp = os.time(),
}
Framework.Functions.Notify("Remembered: " .. ctx.message)
end,
})
Commands.client("recall", {
handler = function(ctx)
local sid = GetPlayerServerId(PlayerId())
if PlayerStates[sid] then
Commands.sendProximity('cmd:me', {
ctx.author,
"recalls: " .. PlayerStates[sid].lastMessage
}, ctx.range)
end
end,
})
NUI Callbacks
Interact with the chat UI via NUI:
RegisterNUICallback('onChatMessage', function(data, cb)
local message = data.message
local author = data.author
-- Process the message
cb('ok')
end)
Logging Integration
Access logged messages programmatically:
-- Listen for logged messages
AddEventHandler('chat:logged', function(source, templateId, args, channel)
print("Message logged:", templateId, channel)
end)
Performance Optimization
Disable Unused Features
Config.Modules = {
mask = false, -- Disable if not needed
friends = false, -- Disable if not needed
bubble = false, -- Disable if not needed
}
Reduce Bubble Render Distance
Config.Bubble = {
range = 10.0, -- Reduce for performance
}
Limit Message History
-- In client settings
Config.UI.Features.MaxHistoryMessages = 5 -- Default 10
Custom Framework Integration
Support frameworks other than QB/ESX:
-- Detect custom framework
Framework = {
GetName = function()
return exports['my_framework']:GetPlayerName()
end,
IsAdmin = function()
return exports['my_framework']:IsAdmin()
end,
}