TLDR cheat sheet
Description | Legacy Example | TextChatService Example |
---|---|---|
Sending Messages | RemoteEvent:FireServer(chatMessage) |
TextChannel:SendAsync(chatMessage) |
Receiving Messages | RemoteEvent.OnClientEvent |
TextChannel.MessageReceived or TextChatService.MessageReceived
|
Display System Messages | - | TextChannel:DisplaySystemMessage(message, metadata) |
Customizing Messages | Speaker:SetExtraData |
TextChannel.OnIncomingMessage or TextChatService.OnIncomingMessage
|
Display Chat Bubble | Chat:Chat(part, message) |
TextChatService:DisplayBubble(part, message) |
Start of Guide
In light of the recent announcement, I’ve noticed that there’s a bit of confusion on how to use the TextChatService. Having spent some time with it, I’m happy to report migrating your experience to the new service is relatively straightforward once you know how it works.
I’ve found this system to be pretty flexible and have yet to run into a situation where I couldn’t replicate the functionality of the old chat system.
Here are a few quick-start guides depending on your current setup. I’ll probably be editing this post as I go along.
Rendering non-chat messages
System Messages
Sending system messages, only works on client. For server-side, use RemoteEvent to raise an event to the client, and then use TextChannel to display it.
Tip: If you are using the default text channels, you can use RBXSystem to render system messages. If you pass in “Error” as part of the second metadata argument, it will render as red!
These messages won’t be filtered or localized. The first argument is the text to be displayed. The second argument is the “metadata” argument which you can use to identify different types of messages in OnIncomingMessage callbacks.
-- client
SystemTextChannel:DisplaySystemMessage(message, "System.Error")
In this example, we detect if the message is a system message and check it’s metadata. You won’t need to do this for RBXSystem, the default TextChannel.
-- client
SystemTextChannel.OnIncomingMessage = function(message)
if message.TextSource == nil then
-- its a system message
if message.Metadata == "System.Error" then
-- make it red
local properties = Instance.new("TextChatMessageProperties")
properties.Text = `<font color="#FF0000">{message.Text}</font>`
return properties
end
end
return nil
end
TextChannels
In addition to the default TextChannels created from TextChatService.CreateDefaultTextChannels, you can create your own TextChannels for your own purposes.
There doesnt seem to be any limitation on how many TextChannels you can have. You’ll just want to make sure they are parented to TextChatService. You can determine who is part of a TextChannel by looking at which TextSource children they have. A TextSource represents a player in a TextChannel. A player can be in many TextChannels at a time.
To add a TextSource to a TextChannel, call TextChannel:AddUserAsync(player.UserId)
from the server.
To remove a TextSource from a TextChannel, just destroy it from the server.
When a message is sent through a TextChannel, only players with a TextSource for that TextChannel will receive it.
TextChannels can be a great way to organize different chat streams. Since each TextChannel can have its own OnIncomingMessage callback, you can customize the appearance of the messages in each channel independent of others.
ShouldDeliverCallback
One neat thing you can do with TextChannels is to implement a custom TextChannel.ShouldDeliverCallback on the server. You can use this to implement all sorts of custom message filtering logic which is very cool.
The wiki already gives a good example on using this to implement some sort of proximity based chat, but you could also use this to create some interesting asymmetrical chat mechanics.
OnIncomingMessage callbacks
If you’re rendering your own UI, you can probably ignore OnIncomingMessage and just use the MessageReceived event. If you’re using the default TextChatService UI, you’ll want to use the OnIncomingMessage callback to customize the appearance of the chat messages.
When a message is received, there are a few callbacks you can hook into the TextChatService system to customize the message. These callbacks all expect a TextChatMessageProperties
or nil
as a return value. Each callback is called sequentially. Its a good idea to write OnIncomingMessage in TextChannels first, as they will be the most local and have the most context. TextChatService itself has an OnIncomingMessage callback which will run for ALL TextChannels in your experience.
DO NOT YIELD OR THROW IN THESE CALLBACKS. Weird things can happen as indicated by the documentation. If you must yield to determine stuff like asset ownership or group membership, do that query ahead of time and store an attribute or something with the result. (You’ll probably want to do that anyway since then you can reuse that state for other non-chat usecase)
The default TextChannels each have their OnIncomingMessage
callbacks defined. The neat thing is you can overwrite these callbacks to redefine how the messages are displayed in the default channels. Custom TextChannels you create will render messages based on the OnIncomingMessage
callback you define. If none are defined, the default UI will render the messages plainly
Below is a quick reference table of how the default TextChannels use the OnIncomingMessage
callback.
TextChannel | OnIncomingMessage Description |
---|---|
RBXGeneral | Usernames are colored either by the Player’s TeamColor property or by using the name color algorithm from 2005 |
RBXSystem | When DisplaySystemMessage is used on TextChannels, you can pass in a “metadata” value as the second argument. In this channel, it will render all messages grey unless “Error” is found in the metadata string where it will render as red instead. |
RBXTeam | Each team channel will be associated with a team color. Each name will be rendered with [Team] as a prefix in the team’s color |
RBXWhisper | Similar to RBXGeneral, except it will be prefixed with the other player’s name as [To OtherName] |
Migrating to TextChatService
Here are a few quick-start guides depending on your current setup. I won’t be able to cover every edge case, but I’ll try to cover the most common scenarios.
If you have specific follow up questions, feel free to ask in this thread!
You're using Legacy Chat and it's not very integrated in your experience
If you’re using Legacy Lua Chat and not doing anything fancy or forked or using chat tags: You’re in luck, this is likely the easiest type of experience to migrate.
Set TextChatService.ChatVersion
to Enum.ChatVersion.TextChatService
and everything should continue to work out of the box.
You're using Legacy Chat and use SetExtraData to add chat tags
If you want to continue using chat tags, you’ll need to learn how to use OnIncomingMessage callbacks to customize the chat messages in the new chat ui.
Set TextChatService.ChatVersion
to Enum.ChatVersion.TextChatService
and get halfway there.
The OnIncomingMessage
callback is where you can customize the chat messages. You can use this to add chat tags and customize the appearance of the chat messages. The default TextChatService UI relies on rich text tags to format the messages. This is nice because it gives flexibility on how you can render the messages and formatting is all done in a centralized place.
The easiest way to replicate the old SetExtraData method is to add attributes to the Player instance. You can then use these attributes in the OnIncomingMessage callback to customize the chat messages.
local Players = game:GetService("Players")
local OWNER = 12345 -- your userId here
Players.OnPlayerAdded:Connect(function(player)
-- you could check for group membership or asset ownership here as well
if player.UserId = OWNER then
player:SetAttribute("ChatTags", "<font color='rgb(255, 255, 0)'>[Owner]</font>")
player:SetAttribute("NameColor", Color3.fromRGB(255, 0, 0))
player:SetAttribute("ChatColor", Color3.fromRGB(255, 0, 0))
end
end)
IMO I would probably store stuff like “isOwner” or “isVIP” as an attribute instead and have all of my text-ui-formatting code live in the OnIncomingMessage callback but you do you.
local TextChatService = game:GetService("TextChatService")
local Players = game:GetService("Players")
TextChatService.OnIncomingMessage = function(textChatMessage)
local textSource = textChatMessage.TextSource
if textSource then
local player = Players:GetPlayerByUserId(textSource.UserId)
if player then
local properties = Instance.new("TextChatMessageProperties")
properties.PrefixText = textChatMessage.PrefixText
properties.Text = textChatMessage.Text
local tags = player:GetAttribute("ChatTags")
if tags and typeof(tags) == "string" then
properties.PrefixText = `{tags} {properties.PrefixText}`
end
local nameColor = player:GetAttribute("NameColor")
if nameColor and typeof(nameColor) == "Color3" then
properties.PrefixText = `<font color='#{nameColor:ToHex()}'>{properties.PrefixText}</font>`
end
local chatColor = player:GetAttribute("ChatColor")
if chatColor and typeof(chatColor) == "Color3" then
properties.Text = `<font color='#{chatColor:ToHex()}'>{properties.Text}</font>`
end
return properties
end
end
return nil
end
You've made a totally custom chat system
The main thing you’ll need to do is to replace your RemoteEvents with TextChannels. TextChannels are the new way to send messages to the chat service.
You have a few options when it comes to TextChannels. You can use the default TextChannels that come with the TextChatService (toggled with TextChatService.CreateDefaultTextChannels
), or you can create your own custom TextChannels.
Since your UI is already handling how messages are displayed, you can likely ignore the OnIncomingMessage
callback and just use the MessageReceived
events to render the messages.
The main thing you’ll have to do is replace your RemoteEvents with TextChannels. You can use the SendAsync
method to send messages to a TextChannel instead of firing the RemoteEvent
. You can use the MessageReceived
event to receive messages from a TextChannel.
Bonus: Your UI has some special code to render some message in special ways
If your RemoteEvent is firing something like:
RemoteEvent:FireServer(chatMessage, { color = Color3.fromRGB(255, 0, 0) })
or some other additional data alongside the message, you may have to do a little more work, but it should be relatively straightforward.
Just like TextChannel:DisplaySystemMessage
, you can also append additional metadata to TextChannel:SendAsync
with the second argument.
-- in your TextBox code:
TextChannel:SendAsync(chatMessage, "Color.Red")
-- in your UI code:
local function createChatTextLabel(textChatMessage: TextChatMessage)
local label = Instance.new("TextLabel")
label.Text = textChatMessage.Text
if textChatMessage.Metadata == "Color.Red" then
label.TextColor3 = Color3.fromRGB(255, 0, 0)
else
label.TextColor3 = Color3.fromRGB(255, 255, 255)
end
return label
end