§ How to · with Picoo
How to customise chat in Roblox (TextChatService)
By Sametcan Tasgiran, Founder & Developer·Published ·Updated
Most Roblox chat tutorials you will find describe a system that no longer runs.
TextChatService replaced the legacy Chat/ChatService module entirely, and almost everything you want — tags, commands, channels — is now a property or an instance rather than a forked module script.
TextChatService, not legacy
The example is built on the current API, so nothing is forked from the old Chat modules.
Rich name tags
Role and rank prefixes added in OnIncomingMessage on the client, read from an attribute the server set.
Slash commands
TextChatCommand instances whose Triggered handler checks permissions on the server.
Channels
Team, party or global TextChannels players can switch between.
Filtering kept intact
Player messages stay in TextChatService, which filters them, and any other player-typed text the example displays goes through TextService filtering first.
System messages
Announcements and join/leave notices in a distinct style.
Example output from a build like this
2 files in this example. Picoo writes this code for your prompt, so file names and sizes vary.
ChatSetup
Server: slash commands with permission checks, channels, the ChatTag attributes and system message helpers.
ChatClient
Client: tag decoration in OnIncomingMessage and the channel switching UI.
Sample output: ServerScriptService.ChatSetup + StarterPlayerScripts.ChatClient
-- ChatSetup (Script in ServerScriptService). If a tutorial tells you to copy
-- ChatModules out of Chat and edit them, it predates TextChatService and will
-- not work in a new place. Tags, commands and channels are instances now.
local Players = game:GetService("Players")
local TextChatService = game:GetService("TextChatService")
-- Commands are instances. Created by a server Script, the command fires
-- Triggered on the server, which is where the permission check belongs:
-- a command is just a message a client can send.
local cmd = Instance.new("TextChatCommand")
cmd.Name = "HealCommand"
cmd.PrimaryAlias = "/heal"
cmd.Parent = TextChatService
cmd.Triggered:Connect(function(source: TextSource, _text: string)
local plr = Players:GetPlayerByUserId(source.UserId)
if not plr or not isAdmin(plr) then return end -- never trust the alias
local hum = plr.Character and plr.Character:FindFirstChildOfClass("Humanoid")
if hum then hum.Health = hum.MaxHealth end
end)
-- ChatClient (LocalScript). OnIncomingMessage runs on the client, so the tag
-- is drawn here, from an attribute the SERVER set: a client cannot award
-- itself [OWNER] in anyone else's chat window.
TextChatService.OnIncomingMessage = function(message: TextChatMessage)
local props = Instance.new("TextChatMessageProperties")
local src = message.TextSource
if not src then return props end
local plr = Players:GetPlayerByUserId(src.UserId)
local tag = plr and plr:GetAttribute("ChatTag") -- server-set attribute
if tag then
props.PrefixText = `<font color="#FFD166">[{tag}]</font> {message.PrefixText}`
end
return props
endHow to customise chat in Roblox (TextChatService)
Chat is the one Roblox system where the majority of available tutorials are actively wrong, because the platform replaced it.
The old approach was to copy the ChatModules folder out of the Chat service into your place and edit Lua files that Roblox shipped. Every guide describing that flow — and there are years of them — now describes something that does not run. TextChatService is the current system, and the good news is that everything the fork used to be for is now a supported instance or callback.
Tags are the clearest example. Instead of editing a module to inject a prefix, you handle OnIncomingMessage in a LocalScript and return properties with your own PrefixText, rich text and all. The important detail is where the rank comes from: read it from a server-set attribute or a server lookup, because if the client supplies its own tag, every player is one line of code away from a staff badge.
Commands follow the same shape. A TextChatCommand is an instance with an alias and a Triggered signal, which makes them pleasant to write and easy to misuse. The alias is not authentication — a command is simply a message the client decided to send, so the permission check belongs inside the handler on the server, exactly as it would for a remote.
Filtering is where the platform rules bite. Messages going through TextChatService are filtered for you. Text you take from a TextBox and render yourself — a pet name, a sign, a custom nameplate — is not, and showing it to other players unfiltered is against Roblox policy no matter how harmless the intent. Anything a player typed that another player will read goes through the filtering API first.
See more on the Luau generator, the AI Luau script writer, the 3D model generator and the game builder, read how Picoo compares with ChatGPT, or browse the full blog.
Frequently asked
Why doesn't my chat tutorial work any more?+
It is almost certainly written for the legacy Chat service, where customisation meant copying ChatModules into your place and editing them. TextChatService is the current system and works completely differently — tags come from OnIncomingMessage, commands are TextChatCommand instances, and channels are TextChannel instances. Any tutorial telling you to fork a chat module is out of date.
How do I add a rank or role tag?+
Handle TextChatService.OnIncomingMessage in a LocalScript (the callback runs on the client) and return TextChatMessageProperties with a modified PrefixText. Get the rank from a server-set attribute or a server lookup, never from something the client supplies — otherwise anyone can hand themselves a staff tag.
Do I still need to filter text?+
Yes, for any player-authored text you display outside the chat window — a name on a sign, a pet name, a message on a billboard. Chat messages sent through TextChatService are filtered for you, but text you take from a TextBox and render yourself is not, and displaying it unfiltered violates Roblox's rules regardless of intent.
Are chat commands secure?+
A command is a message a client chose to send, so the alias proves nothing about who sent it. Check permissions inside the Triggered handler on the server, exactly as you would for a RemoteEvent.
Can I build my own chat UI?+
Yes — set TextChatService.ChatWindowConfiguration.Enabled to false and drive the messages yourself. You still send through TextChatService so filtering, moderation and channels keep working; you are replacing the window, not the pipeline.
Related Picoo prompts
an admin panel in Roblox Studio
RemoteEvents are callable by anyone. A panel that only hides its buttons from non-admins is decoration, not security — the check has to live on the server.
lobby in Roblox Studio
It applies game_map, Picoo's map kit, whose default theme is this lobby hub: 10 scripts and 3,209 lines in one recipe call. A ready-up queue that teleports a group into a match is a separate system you add with a follow-up prompt.
How to prevent exploits in Roblox
Example output: 4 files · 160 lines of guards. The model writes this code for your game, so the files and line counts vary.