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,
}

Configuration
../configuration/
Integrations
../integrations/