Skip to Content
ReferenceChat Exports & Routing

Use the stock chat provider deliberately

The official export pages  describe five client/server export entries. Detailed hook behavior below is checked against the pinned stock implementation , because a short prose summary does not expose every routing boundary.

Export signatures and sides

ExportSideContract
addMessage(message)ClientDisplay a message through this client’s stock chat resource.
addMessage(target, message)ServerSend to the target server session ID; -1 explicitly broadcasts. The provider also supports a one-argument broadcast overload, so retain an explicit target for private messages.
addSuggestion(commandName, helpText, parameters)ClientAdd command-completion metadata. Parameter entries have name and help; a suggestion does not register the command or authorize its use.
registerMessageHook(callback)ServerRegister a callback with (source, outMessage, hookRef) for messages passing through the stock routing pipeline.
registerMode(modeData)ServerRegister a named mode with display metadata and a callback. A missing name, display name or callback returns false in the provider.

Message objects can contain args, a color, multiline and a selected template. Treat text as untrusted content, preserve argument types and do not let a player supply arbitrary template HTML. Server IDs, client player indices and display names are not interchangeable recipients.

The server addMessage export directly triggers chat:addMessage on the recipient. It does not run every message through registerMessageHook. Therefore a hook is not a universal outgoing-message filter for all resources. Apply sensitive-message policy at its actual producer as well as at the user-chat boundary.

Hook operations

HelperActual provider behavior
updateMessage(fields)Shallow-merge the supplied fields into the outgoing message, rather than replacing the whole object. params is merged by key. A template containing {} composes with the previous template or the default marker.
cancel()Mark this routed message as cancelled. Other registered hooks can still execute before the routing function returns.
setSeObject(aceObject)Select recipients matching the ACE object. It does not independently authorize the sender.
setRouting(target)Replace the recipient selection with one ID, -1, or a list of IDs. A later routing change is not automatically intersected with an earlier ACE filter.

Hooks are iterated through the provider’s table; do not rely on a security-sensitive ordering between independently registered hooks. A mode callback runs after the general hooks in the reviewed implementation. An ACL filter or cancellation mechanism does not validate arbitrary parameters by itself.

This server-side hook cancels overly long user text passing through that pipeline. It handles both an author/message pair and a message-only argument array without assuming args[2] always exists:

exports.chat:registerMessageHook(function(_, outMessage, hookRef) if type(outMessage) ~= 'table' or type(outMessage.args) ~= 'table' then hookRef.cancel() return end local text = outMessage.args[#outMessage.args] if type(text) ~= 'string' or #text > 300 then hookRef.cancel() end end)

It is not a complete moderation system, rate limiter or replacement for validating a custom chat resource. The stock implementation associates hooks with the invoking resource and removes them when that resource stops. Register once during startup, not once per player or every tick. No public unregister token is returned by this implementation.

Permission-scoped modes

modeData uses name, displayName, color, seObject and cb. The callback receives (source, message, cbs), with the same helper family described above. In the reviewed provider, seObject limits mode visibility and checks the sender before routing; recipient selection must also be deliberate in the callback.

exports.chat:registerMode({ name = 'docs_staff', displayName = 'Staff', color = '#d4d4d4', seObject = 'chat.staff', cb = function(source, _, cbs) if not IsPlayerAceAllowed(source, 'chat.staff') then cbs.cancel() return end cbs.setSeObject('chat.staff') end })

Grant chat.staff only to the intended principals through ACE permissions. Test a permitted sender/recipient, an unpermitted sender, an unpermitted observer and permission changes while connected. UI visibility alone is not an authorization boundary. Choose a unique mode name; the provider stores modes by that name rather than automatically preventing collisions between resource owners.

The source implementation additionally transports isChannel and isGlobal display flags. Those flags are not replacements for the sender/recipient policy. Resource shutdown removes owned modes and notifies clients. Restarting the chat provider can require consumers to re-register their modes; declare dependencies and test that lifecycle.

Events and troubleshooting

Use chat:addMessage, suggestions, template registration and the legacy chatMessage event for their individual contracts. Template selection and template creation are different operations.

When a private message appears publicly, inspect the server export’s recipient and overload first. When a hook appears to miss a message, inspect whether the producer bypassed the user-chat routing pipeline. When a stopped resource’s hook still appears active, check for duplicate provider copies or a replacement implementation before assuming stock teardown behavior. These are source-backed contracts; the page does not claim an in-game chat/session test already ran.