Changing chat type and other chat settings without forking

The Legacy Chat System (formerly Lua Chat System) is deprecated and no longer officially supported by Roblox. This tutorial is for the last-supported version of the Legacy Chat System. For all new work, please refer to the TextChatService documentation for rough equivalents.

If you’re new here, I encourage reading through the preface introducing the method of changing chat settings. If not and you want to skip to the settings, here’s a list of all the sections!

  1. Chat Behaviour: General settings for using the chat.
  2. Chat Text Size Settings: Most text sizes. Does not affect old bubble chat.
  3. Font Settings: Overall font. Does not affect old bubble chat.
  4. Color Settings: Chat window and bar colours.
  5. Window Settings: Chat window size settings.
  6. Fading Settings: Determines fading when chat window is inactive.
  7. Channel Settings: General settings for all channels and their help text.
  8. Message Settings: Configurations for chat messages.
  9. Misc Settings: Settings without a category attached. Miscellaneous.
  10. Display Name Settings: Compatibility for display names as rolled out in 2021.

A popular conversation brought up on the Developer Forum is regarding the modification of the Lua Chat System. Furthermore, within that topic, developers often find themselves wanting to change the chat style between Classic, Bubble and Both. This is due to an update that removed that option from the game settings page in hopes of a better way to configure chat properties in the future.

A common solution I’ve seen being provided to developers is to fork the chat system’s contents and modify the booleans ClassicChatEnabled and BubbleChatEnabled in the ChatSettings module. I don’t like this solution for two reasons.

  1. You should not be forking the entire Lua Chat System just to modify two settings. It is possible to change chat types by only forking the ClientChatModules folder and only including ChatSettings in it. This forks that module alone and the rest are inserted as necessary.

  2. You should not be forking systems for minor edits. Forking prevents updates to a system and thus you are held responsible for manually updating anything changed. If you don’t fork, you get the code inserted directly from the latest CoreScripts branch, which means automatic updates.

With this in mind, I’d like to share with you Chat.RegisterChatCallback. While there is documentation that briefly explains what you can do with the function and what it’s used for, I often feel that it’s not explained fully. For this thread, I will be explicitly explaining the option OnCreatingChatWindow. A code examples thread for RegisterChatCallback as a whole is for another time.

Let’s grab the documentation for RegisterChatCallback and the OnCreatingChatWindow section.

RegisterChatCallback binds a function to some chat system event in order to affect the behavior of the Lua chat system. The first argument determines the event (using the ChatCallbackType enum) to which the second argument, the function, shall be bound. The default Lua chat system uses InvokeChatCallback to invoke registered functions. Attempting to register a server- or client- only callback on a peer that isn’t a server or client respectively will raise an error. The following sections describe in what ways registered functions will be used.


Client-only. Invoked before the client constructs the chat window. Must return a table of settings to be merged into the information returned by the ChatSettings module.

The documentation is self explanatory and sufficient here. RegisterChatCallback binds a function to a specific ChatCallbackType and the chat system used InvokeChatCallback to run your functions when specific conditions are met. The condition is typically the ChatCallbackType Enum which summarises very well what something is. I shouldn’t have to explain what OnCreatingChatWindow means.

Now, the thing about OnCreatingChatWindow is that it is able to modify the ChatSettings. How can we use this to our advantage and change the chat type? We simply bind a function that returns a table of settings to be modified.

Let’s change our game’s chat type to Bubble Chat for our example. In a LocalScript preferably under ReplicatedFirst, set this up:

local Chat = game:GetService("Chat")

Chat:RegisterChatCallback(Enum.ChatCallbackType.OnCreatingChatWindow, function()
    return {
        ClassicChatEnabled = false,
        BubbleChatEnabled = true,

Keep in mind that OnCreatingChatWindow can only be used on the client. Don’t write it in a server script, that’ll produce an error.

And that’s it. All you need to know. Have fun. All that text just to lead up to this one small piece of code and not even a satisfactory conclusion. Wow!

Before you go though, there is one last thing that may be of interest to you; remember that I said this has the capability to modify the chat settings. This means that you can return any number of key-value pairs to edit the chat system with.

Here is a full list of the possible settings keys you can include in your settings return table. This is mainly if you’re looking for a reference or you haven’t forked the chat system before and taken a scroll through the contents of ChatSettings.

I’ve taken the liberty to not waste time and explain self-explanatory labels, for the most part.

Chat Behaviour

Key Summary Value type Default value
WindowDraggable Determine whether clients can freely move their chat windows. Boolean False
WindowResizable Spawns a tab in the bottom-right corner of the chat window, allowing the user to push or pull it to change the size of their chat window. Boolean False
ShowChannelsBar A list of non-private chat channels will be shown at the top of the chat window. Further behaviour modifications available. Boolean False
GamepadNavigationEnabled This allows for navigation of the chat window with game pads. A strong doubt you’d ever need it, but someone may. Boolean False
AllowMeCommand Enable the dreaded /me [text] command for players to use. Added due to abuse by bad actors. Boolean False
ShowUserOwnFilteredMessage Determines if users see their own messages as filtered or not. Still appear filtered to other users. You should probably just leave this alone. Boolean True
ChatOnWithTopBarOff If you ever use SetCore(“TopbarEnabled”, false) but want the chat to still work, this is for you. Boolean False
ScreenGuiDisplayOrder Modifies the display layering for the ScreenGui. You probably won’t need to touch this. Number 6
ShowFriendJoinNotification Will send a message to your window if a friend joins your game. Boolean True
BubbleChatEnabled The one you probably wanted to see. Self explanatory. Boolean IND
ClassicChatEnabled Same as above. Boolean IND

Chat Text Size Settings

Key Summary Value type Default value
ChatWindowTextSize Size of chats in the chat area. Number 18
ChatChannelsTabTextSize Size of the channel labels. Number 18
ChatBarTextSize Size of the text you type in the bar. Number 18
ChatWindowTextSizePhone ChatWindowTextSize for mobile. Number 14
ChatChannelsTabTextSizePhone ChatChannelsTabTextSize for mobile. Number 18
ChatBarTextSizePhone ChatBarTextSize for mobile. Number 14

Font Settings

Key Summary Value type Default value
DefaultFont Universal font. Enum.Font SourceSansBold
ChatBarFont Font for the chat bar. Enum.Font SourceSansBold

Color Settings

Key Summary Value type Default value
BackGroundColor Chat window background. Color3 new(0, 0, 0)
DefaultMessageColor Default message colour if ExtraData not set. Color3 new(1, 1, 1)
DefaultNameColor Default name colour if ExtraData not set. Color3 new(1, 1, 1)
ChatBarBackGroundColor Background surrounding chat bar. Color3 new(0, 0, 0)
ChatBarBoxColor Actual chat box. Color3 new(1, 1, 1)
ChatBarTextColor Text inputted into the chat bar. Color3 new(0, 0, 0)
ChannelsTabUnselectedColor Unselected channels. Use if ShowChannelsBar is on. Color3 new(0, 0, 0)
ChannelsTabSelectedColor Same as above, except for a selected channel. Color3 fromRGB(30, 30, 30)
DefaultChannelNameColor Self explanatory. Color3 fromRGB(35, 76, 142)
WhisperChannelNameColor Channel when a whisper is open. Color3 fromRGB(102, 14, 102)
ErrorMessageTextColor Errors sent in the chat area. Color3 fromRGB(245, 50, 50)

Window Settings

Key Summary Value type Default value
MinimumWindowSize UDim2 {0.3, 0}, {0.25, 0}
MaximumWindowSize UDim2 {1, 0}, {1, 0}
DefaultWindowPosition UDim2 {0, 0}, {0, 0}
extraOffset Calculation to fit chat bar. Number (7 * 2) + (5 * 2) or 24
DefaultWindowSizePhone UDim2 {0.5, 0}, {0.5, extraOffset}
DefaultWindowSizeTablet UDim2 {0.4, 0}, {0.3, extraOffset}
DefaultWindowSizeDesktop UDim2 {0.3, 0}, {0.25, extraOffset}

Fading Settings

Key Summary Value type Default value
ChatWindowBackgroundFadeOutTime Time until background fades. Number 0.5
ChatWindowTextFadeOutTime Time before window fades if no interaction with the chat window is made. Number 30
ChatDefaultFadeDuration How long fades last. Number 0.8
ChatShouldFadeInFromNewInformation Show chat if new information is sent. Boolean False
ChatAnimationFPS A reference number for the fade animation’s smoothness. Should leave this one be. Number 20.0

Channel Settings

Key Summary Value type Default value
GeneralChannelName Default global chat room. If set to nil, a global chat will not be made, giving you the freedom to make your own channels (e.g. dead/alive for a round-based game). String or nil All
EchoMessagesInGeneralChannel Determines if messages from other channels should show up in the general channel. Set to false if ShowChannelsBar is true to keep messages in their respective rooms. Boolean True
ChannelsBarFullTabSize Number of channels that can be fit into the bar before scrolling enables. Number 4
MaxChannelNameLength Use with ShowChannelsBar as true. Determines max characters to be shown in a channel bar button before truncation. Number 12
MaxChannelNameCheckLength Maximum characters a channel name can have. Set to 50 for whisper channel compatibility, should leave this as is. Number 50
RightClickToLeaveChannelEnabled Use with ShowChannelsBar as true. Self explanatory. Boolean False
MessageHistoryLengthPerChannel How many messages are shown before old chats are deleted. Number 50
ShowJoinAndLeaveHelpText If help text for joining and leaving channels should be shown. Useful only for custom channels. Boolean False

Message Settings

Key Summary Value type Default value
MaximumMessageLength Maximum characters in a chat line. Probably should leave this alone. Number 200
DisallowedWhiteSpace Prevent this type of whitespace from being entered into chats. Table n, r, t, v, f
ClickOnPlayerNameToWhisper Clicking on a name enters a whisper channel with the other player. Boolean True
ClickOnChannelNameToSetMainChannel Clicking on a channel name prefixing a message (e.g. {Team} <Msg>) enters that channel. Similar to clicking player names to whisper chat them. Boolean True
BubbleChatMessageTypes Internal configuration for bubble chats. Table N/A

Miscellaneous Settings

Key Summary Value type Default value
WhisperCommandAutoCompletePlayerNames Typing /w [partialName] autocompletes and enters a whisper with that user. Boolean True

Display Name Settings

Note on Display Name Settings: The current existing display name settings are controlled by FFlags (fast flags), internal markers that Roblox can use to quickly enable or disable features in production builds. If you do not override these values yourself then they will use the value of the FFlag Roblox has set. The FFlag used is UserChatDisplayNamesEnabledByDefault. A quick way to check the flag’s value is through evaera’s FFlag Watcher.

Key Summary Value type Default value
PlayerDisplayNamesEnabled Uses display names in the chat window instead of usernames. Boolean IND
WhisperByDisplayName Allows whisper chat command to additionally work with display names. Usernames are still enabled and default behaviour for the whisper command. Boolean IND

Let me know if you have questions, comments, concerns or any other type of feedback. Let’s keep it constructive and relevant. Happy developing!


I honestly never knew that the Chat service had any events or function, nor did I expect that you could do this all the time. Now I only have to fork the chat when I want to give players a tag or a custom chat color, unless you have also something for that in your bag. But short question, does RegisterChatCallback need to be called before the window got created, or can I do it anytime? (Can’t test it out right now, Studio completely :b:roke)


RegisterChatCallback itself is used, as its name states, to register callbacks to the chat. The first argument of RegisterChatCallback accepts a ChatCallbackType Enum, so depending on which one you decide to use, that changes the necessities for your code. Specifically for OnCreatingChatWindow, the chat service calls InvokeChatCallback and fires off any functions registered to OnCreatingChatWindow before the chat window is created.

We keep our script in ReplicatedFirst so that the function is registered before the window is created. The same window of opportunity may not exist when placed elsewhere due to potential race conditions. Remember, the Lua Chat System and the LocalScript would be two entry points of code. There’s no guarantee your script will run before the setting of the chat window does. Placing it elsewhere though is worth a test.

As far as giving players tags and custom chat colours, that’s worth a quick tutorial on its own, but it is possible to give these items without forking the chat system as well. In fact, I currently do that right now for a group that I currently develop for. You are able to change these kinds of message appearances with another ChatCallbackType, OnClientFormattingMessage. I don’t typically have a use for it.

I’ll consider using my GitHub more actively seeing that I’ve been posting a lot of tutorial content recently, maybe I post the code there too. For now though, it may be slightly off topic, but I can show you how I currently assign tags.

The code is under this tab. Kept here so as not to disturb thread.
local ServerScriptService = game:GetService("ServerScriptService")

local ChatServiceRunner = ServerScriptService:WaitForChild("ChatServiceRunner")
local ChatService = require(ChatServiceRunner.ChatService)

local SPECIAL_DATA = {
    [6809102] = {
        ChatColor ="Persimmon").Color,
        Tags = {
                TagText = "Developer",
                TagColor ="Lime green").Color,
        -- Other tags here
        -- Other message formats here
    -- Other user here

local function handleSpeaker(speakerName)
    local speaker = ChatService:GetSpeaker(speakerName)
    local player = speaker:GetPlayer()

    if player then
        local extraData = SPECIAL_DATA[player.UserId]
        if extraData then
            for key, value in pairs(extraData) do
                speaker:SetExtraData(key, value)

for _, speakerName in pairs(ChatService:GetSpeakerList()) do

Will consider removing the above code and moving this to a separate tutorial another day.


Is there anyway to set it to work for players with a set gamepass?

The chat tag code? Yes. The whole condition under if player then is subject to whatever checks and sets you want to apply to speakers. It’d be appreciated if you started a new thread if you were curious on how to do that though. The main point of this tutorial is chat settings, not message formatting. I just took a bit of time to answer that question quickly.

1 Like

I have found the answer to this API: Its the framerate of fading animations in the chat UI.

Do you know how specifically it works? I figure it’s related to some kind of animation FPS as per the name but I haven’t been able to crack the code on what values this contributes to, what changing it does and why the API is this way instead of just a value to determine over how long an animation should take to finish.

1 Like

So when you stop hovering over the chat long enough, it starts to fade, then when you hover over it again it pops right up. If you change the value of the API to say like 1 FPS. The chat fading animation will be super slow and it will be sorta lagging.

1 Like

I see, thanks for the information! I’ll try experimenting around with the values and checking the source, then make an update on the post clarifying what the setting is for.

1 Like

If you want the source its here: Customizing In-Experience Text Chat | Documentation - Roblox Creator Hub. Scroll down to the api reference: ChatFadingFPS.

I looked at that page already. The goal of listing all the settings via this tutorial is to reiterate the purpose of each setting in a quick, concise, accessible and easily understandable manner. :slight_smile:

1 Like

Hey developers! I just got a question about using this tutorial to make some edits to the ChatService and I made a new discovery while helping them that’s relevant to the thread. I will be updating it accordingly!

For the GeneralChannelName field, setting this to nil will remove the general channel. It will leave only the system channel then. Without the general channel, you can do your own management of chat channels, such as creating channels for alive and dead players in a round-based game where dead players not communicating with alive players is critical.

The purpose of the general channel is to echo messages from other channels, as well as to allow a hub for sending all messages. When you see the braces wrapping text before chat, that’s the echo effect. For example: when you see {Team} [colbert2677]: Hello!, this is the general channel catching a message from the Team channel. You can disable this behaviour with EchoMessagesInGeneralChannel, by the way.

The system channel’s use is self explanatory: sending system messages. For example, the message that a friend joined your server is a message in the system channel. You can simply call ChatService:RemoveChannel("System") to get rid of it, though any method trying to send a system message will throw a warning in the server console. You’ll have to fork to fully remove any uses of the system channel.

I originally did make note of what setting GeneralChannelName to nil does, but it was incorrect: it doesn’t prevent chats from other channels showing up, it completely disables the channel. So, with this new information in mind, I will be updating that table.


Post is outdated:

PlayerDisplayNamesEnabled and WhisperByDisplayName are now available to edit, false is the default for both of these (as of May 2021, I believe this is true). Doesn’t look to be documented on Lua Chat System — Client API — ChatSettings yet. (still not documented in June 2021 despite being out to most users)


Thank you for your post. I’ve noticed these settings a bit back but forgot to include them in the post since I don’t typically maintain old tutorials unless there’s a major point to be made. I’ll find some time soon to incorporate these two items into the post, along with any other potential settings changes.

The ChatSettings article itself pretty much makes this tutorial obsolete (and I didn’t even recognise the page existed at the time of writing, or maybe it didn’t, not sure). If you’d like, you can file a Developer Hub thread to request for an update to the ChatSettings article with the new settings.


I noticed those around October but I didn’t sadly see this article before. I’ve personally never seen the ChatSettings article before, until I saw it referenced in post #9 I think. I highly doubt Roblox will accept a request to update the DevHub page with the display name stuff as it is technically an unannounced feature despite people leaking it for a few months.

Hey! I have a question. So I have created a party System similar to the one on Minecraft Hypixel and I created a system to make channels so that players can chat separately in tabs in their respective parties. Everything is working except for one issue. When a party is disbanded and I delete the party channel each member is in, if a player still has the channel tab open, it remains there. They can type messages but they can’t send messages. I want to be able to find a way to delete the tab even if they are viewing/typing in it.

I think you have to remove the player from the channel, checking if the speaker is in the channel using IsInChannel and LeaveChannel

1 Like

Sorry for the late response. I do remember seeing this post and wanting to respond but never getting around to it… I just remembered now while browsing the Lua Chat System article.

I recall this issue as well, the channel tab hangs around when players leave the channel but it’s in all respects “disabled”. You will want to use SetMainChannel to force the user to select a different channel. You’ll notice that this is also used in other cases like entering team or whisper chat when prefixing your message with the relevant command (/t, /w). In fact, the channel bar buttons specifically set the main channel. Main, in this case, refers to the current talking channel.

Combine the above with ChannelLeft (or use the channel specific version, SpeakerLeft). When the speaker leaves the channel, you can call SetMainChannel to force them back to the default channel or another channel of your choosing. If you use the general channel (All), then you can pass All as the channel name and this’ll force players back to global chat.

Depending on my case I may preference one or the other.

local function speakerLeft(speakerName)
    local speaker = ChatService:GetSpeaker(speakerName)
    if speaker then

local function channelLeft(channelName)

This should clear the left channel from the tabs since, if I recall correctly, the channel tab doesn’t clean itself up until you switch channels. Feel free to give that a try.

@colbert2677 are you going to get around to adding these yet considering it’s public now? Not sure if chat commands are documented on DevHub yet (it’s not)

1 Like

i forgot :laughing:

I’ll add the properties now and check if there are any additional information changes to be made. Thank you for the reminder!

EDIT: The changes are now in as Display Name Settings. I’ve also additionally added section headers that users can jump to via a table of contents at the top (no return buttons yet?) and an additional new setting that was added, MaxChannelNameCheckLength. This seems to be the new property that enforces how many characters a channel name can be, whereas MaxChannelNameLength is now used to determine how many characters will show in a button in the channel bar before it gets truncated.

Marking as solution for a while.