A modular Roblox chat system that replaces the default chat with rich text, markdown formatting, custom emojis, message reactions, bubble chat, context menus, and multilingual localization.
Config/ ← Custom emoji definitions + server bootstrap
Loader/MainModule/
init.lua ← Server entry point
shared/ ← Shared utilities (both sides)
server/ ← Server-only logic
client/ ← Client UI + event handling
The system is split into a server side (channel management, filtering, message reconstruction) and a client side (UI rendering, input handling, bubble chat). They communicate via a thin networking abstraction over Roblox RemoteEvents/RemoteFunctions.
| File | Purpose |
|---|---|
Config/init.lua |
Defines custom emoji mappings (e.g. :troll: → asset ID). No dependencies. |
Config/Loader/init.server.lua |
Server entry point. Requires MainModule and passes the config to it to start the system. |
These modules are used by both server and client.
Networking abstraction over RemoteEvents and RemoteFunctions. Hides raw Roblox networking behind a clean API so callers don't deal with RemoteEvent/RemoteFunction directly.
- Server side:
newEvent,newFunction,newUpdater,fireClients,update - Client side:
fire,invoke,onClientEvent,hookUpdater - The "updater" pattern (
newUpdater/hookUpdater) is a bi-directional request-response: client subscribes and immediately requests current state, server pushes updates on change.
Custom signal/event implementation similar to RBXScriptSignal. Batched, yield-safe, uses coroutine reuse.
Signal.new()→Connect,Fire,Wait,Once,DisconnectAll- Used throughout the codebase wherever Roblox's native events aren't sufficient (e.g. cross-module communication).
Minimal XML parser. Converts XML strings into nested Lua tables. Used for parsing RichText tags inside messages.
MIT-licensed MD5 hashing library. Present for integrity/ID purposes but not central to the main chat flow.
Core text processing module. Factory function — initialized with config, returns a richText object used by both server and client.
Key functions:
escape(text)/unescape(text)— sanitize XML special charactersmarkdown(text)— converts markdown to RichText XML (delegates to markdown.lua)colorize(text, rgb)— wraps text in a<font color>tagstrip(message)— removes all XML tags (plain text extraction)processForFilter(message, player)— prepares a message for Roblox's text filter; handles emoji substitution and escapingsplitXmlAndText(input)— splits a formatted string into alternating XML-tag and text segments
Converts markdown syntax to Roblox RichText XML. Supports:
| Syntax | Output |
|---|---|
**text** |
Bold |
*text* / _text_ |
Italic |
***text*** |
Bold + Italic |
__text__ |
Underline |
~~text~~ |
Strikethrough |
(R,G,B / text) or (#HEX / text) |
Colored text |
[color / text] |
Outlined (stroke) text |
<font / text> |
Custom font |
Implemented as a recursive depth-based formatter. Returns an object with a single format(text) method.
Color utility helpers.
hex_to_rgb/rgb_to_hexmatchBrickcolor(name)— fuzzy-matches a string to a Roblox BrickColor name. Pre-caches all 1032 BrickColors indexed by first letter for performance.
Main server initialization. Sets up the entire server-side chat infrastructure:
- Configures
TextChatServicewith a TextChannels folder - Creates the system
RBXSystemchannel - Initializes shared modules: signal, network, richText, md5
- Exposes
getConfigRemoteFunction → returns custom emoji config to clients - Sets up
typingIndicatorRemoteEvent - Creates the default "Main" channel (auto-join enabled)
Factory for creating chat channels. channel.new(name, autoJoin) creates a TextChannel with:
ShouldDeliverCallback— intercepts each message before delivery, extracts the original text from metadata, runs it through the filter, and stores a reconstruction function so the client can restore formatting after filteringsourceAdded/sourceRemovedsignals — track which players are in the channel- Auto-join: connects
PlayerAddedto add sources; removes sources on leave
Exposes two remote endpoints:
getChannelsIn(player)— returns the list of channels the player belongs toreconstruct(player, messageId, filteredText)— called by clients after receiving a filtered message; returns the fully-formatted RichText version
Broadcasts reactions:
reactToMessage(player, messageId, reaction)— receives a reaction from one client, broadcasts it to all others
Builds the entire chat UI hierarchy programmatically (no Roblox Studio design file). Creates:
Mainframe (400×300) — the chat windowScroller— ScrollingFrame for the message listChatbar— text input area with an ActionIcon buttonChannelbar— channel selector (hidden when only one channel exists)Autofill— dropdown for emoji/text autocompleteContextMenu— right-click / long-press menu (Reply, Edit, Delete, React)
LocalScript entry point. Requires chat/init.lua, creates the UI from Chat.lua, parents it to PlayerGui, and shows the welcome message.
Wires up all client components into a single chatVariables object that is passed to every submodule. Loads:
chatbar,autofill,channelBar,chatWindow,bubbleChatcomponentslocalization,network,customEmojiHandlermessageCreatorsandcoreConnections
Exposes :systemMessage(text) for injecting system-level messages (e.g. friend join notifications).
Text input management.
- Dynamic height: grows as the user types multi-line messages
- 185 character limit
- Live markdown preview as you type
- Typing indicator: fires
typingIndicatortrue on focus, auto-cancels after 5 seconds of no typing, fires false on blur - Enter to send; sends via the current
TextChannel - Supports action context (Reply / Edit):
:assignActionImage(imageId, context)sets the ActionIcon and stores what action will happen on send;:unassign()clears it
Channel selector strip. Adjusts layout when shown/hidden to avoid overlapping the scroller.
Visual state management for the message area.
- Hover: shows scrollbar thumb, dims window slightly
- Focus (chatbar active): darkens background
- All transitions use smooth tweens
Autocomplete dropdown. Extension-based — loads handlers from extensions/autofills/.
- Dynamically generates buttons from a list of options
- Keyboard navigation: Up/Down arrows, Tab, Right arrow to confirm
- Scrolls selected button into view
- Pattern matching: captures text between
:delimiters and sends to emoji extension
Core message rendering logic. new(messageData, isReceived) creates a message UI bubble.
Bubble types:
DefaultBubble— standard message with avatar, username, textReplyStartBubble— first message in a reply chain (shows quoted text above)ReplyingToPreviousBubbleChain— follow-up message to same sender in a reply chain
Message grouping: consecutive messages from the same sender within 50 seconds are grouped (no repeated avatar/name).
Context menu actions:
- Reply — sets chatbar action icon, stores
replyingTocontext - Edit — replaces message text with a live TextBox for editing
- Delete — removes message from UI
- React — sends reaction emoji via
reactToMessagenetwork call
Tracks messages by ID and GUID for edits, reactions, and reconstruction.
Bridges Roblox's CoreGui chat API with the custom chat. Creates BindableEvents/BindableFunctions and registers them with SetCore/GetCore so that game scripts using the standard Roblox chat API continue to work.
Supported surface area: ToggleVisibility, SetVisible, FocusChatBar, ChatBarDisabled, ChatMakeSystemMessage, ChatWindowPosition, ChatWindowSize, IsFocused, and more.
Central message event dispatcher.
- Subscribes to
getChannelsInupdater → gets list of channels - Connects
MessageReceivedon each channel - On each message: invokes
reconstructto get the fully-formatted text, determines if it's a new message or an edit, routes to the correct message creator - Auto-scrolls to the newest message
- Shows
(Edited)label on edited messages - Handles incoming
reactToMessageevents and updates reaction UI
Mouse tracking via Heartbeat. Accounts for GuiInset (topbar height offset).
mouse:detectInObject(object, wrapper)— returns a signal and connection that fire when the mouse enters/exits an objectmouse:getPosition()— current mouse position
Renders messages containing custom emojis by mixing TextLabel and ImageLabel elements inline.
setText(label, text, message)— main entry point; breaks text into segments and renders eachbreakdown(label, text)— splits text at emoji markerscreateImage(label, imageName)— creates a sized ImageLabel for an emoji- Uses a
UIListLayoutfor horizontal arrangement
Object pool for TextLabel and ImageLabel instances. Avoids creating new instances on every render.
cache:store(object)— returns object to poolcache:get(className)— retrieves from pool or creates new
Parses a formatted message string into a list of word segments, each tagged with its formatting (bold, italic, color, stroke) and any embedded emoji. Used by customEmojiHandler/init.lua to lay out inline content.
Connects bubble chat to the main message and typing indicator systems. Creates one bubble controller per player character.
Main bubble chat initializer. Configuration:
| Setting | Value |
|---|---|
| Easing | Bounce |
| Max bubbles | 4 per player |
| Fadeout time | 10 seconds |
| Text size | 16 |
| Background | RGB(20,20,20) |
| Max visibility distance | 40 studs |
Returns a billboard system with create(part) to attach bubbles to a character part.
Manages the vertical stack of bubbles above a player's head.
stack:push(ui)— adds bubble, triggers position tween for all existing bubblesstack:remove(ui)— removes bubblestack:fade(ui, duration)— fades out a bubble (called when MaxMessages is exceeded)stack:update()— recalculates and tweens all bubble Y positions- Only the newest bubble shows the caret (speech bubble tail)
Per-player BillboardGui controller.
- Raycast-based visibility: sets
AlwaysOnTopbased on whether the player has line-of-sight to the speaker - Color theming via
setColors() - Typing indicator:
setTypingIndicatorVisible(state) createMessage(text, message)— adds a new bubble to the stack
Creates bubble UI elements: rounded speech bubbles sized to text, the caret tail, and the animated typing indicator (three dots with staggered tweens).
Thin TweenService wrapper used by the bubble animation system.
Emoji autocomplete extension. Implements the autofill interface:
beginsWith::characterendsWith::character (or mid-word for partial match)onCapture(text): searches the emoji list and returns matching options with images
Merges standard Unicode emojis with custom emojis from config.
Database of 100+ standard emojis, indexed by first letter for fast lookup.
list.search(text)— fuzzy search by name prefixlist:findDirect(text)— exact name lookup
Localization entry point. Fetches a Translator from the LocalizationTable based on the player's locale, falls back to en-us.
module:localize(key, default, ...)— get a translated string with fallbackmodule:getWelcomeMessage()— welcome message with "System" prefixmodule:getChatbarPlaceholder()— chatbar hint textmodule:getMessagePrefix(base)— "System" / "Team" prefixes
Loads all language translation tables and merges them into a single LocalizationTable. Supports 50+ locales including en-us, fr-fr, de-de, ja-jp, zh-cn, ru-ru, es-es, and many more.
User types → chatbar.lua (markdown preview)
→ FocusLost → TextChannel:SendAsync (with metadata)
→ Server ShouldDeliverCallback (filter + store reconstruction fn)
→ MessageReceived on client
→ eventHandler invokes reconstruct()
→ Server returns formatted RichText
→ defaultMessage.lua renders bubble
→ bubbleChat shows above head
User focuses chatbox → fire typingIndicator(true)
→ 5s timeout → fire typingIndicator(false)
→ Server broadcasts to all clients
→ bubbleChat/billboard shows/hides typing dots
User right-clicks message → ContextMenu appears
→ Selects reaction → fire reactToMessage(messageId, emoji)
→ Server broadcasts to all clients
→ eventHandler updates reaction UI on message bubble
Client connects → hookUpdater getChannelsIn
→ Server fires current channel list
→ eventHandler connects MessageReceived on each channel
→ New channels pushed when player joins one
| Name | Type | Direction | Purpose |
|---|---|---|---|
getConfig |
RemoteFunction | Client → Server | Fetch custom emoji config |
reconstruct |
RemoteFunction | Client → Server | Restore formatted message after filter |
getChannelsIn |
Updater | Bi-directional | Channel list subscription |
typingIndicator |
RemoteEvent | Server → Clients | Player typing state |
reactToMessage |
RemoteEvent | Client → Server → Clients | Emoji reactions |