Building a chat system in Bubble involves creating Conversation and Message Data Types, a contacts list with unread badges, a message thread that auto-updates via Bubble's real-time search, and workflows for sending messages. This tutorial covers the complete data model, chat UI layout with a Repeating Group for messages, typing indicators using custom states, unread message counting, and auto-scrolling to the latest message.
Overview: Chat System in Bubble
This tutorial guides you through building a complete one-on-one and group messaging system in Bubble. You will create the database structure, build the chat interface, implement real-time updates, and add polish features like unread counts and typing indicators.
Prerequisites
- A Bubble app with user authentication set up
- Basic understanding of Data Types and Repeating Groups
- Familiarity with Bubble Workflows and custom states
- Users registered in your app for testing
Step-by-step guide
Design the chat data model
Design the chat data model
Go to the Data tab and create two Data Types. First, 'Conversation' with fields: 'participants' (list of Users), 'last_message' (text — preview of the most recent message), 'last_message_time' (date), and 'name' (text — for group chats, optional). Second, 'Message' with fields: 'conversation' (Conversation), 'sender' (User), 'body' (text), 'is_read' (yes/no, default no), and the auto-generated Created Date serves as the timestamp. This two-table structure keeps conversation metadata separate from individual messages for efficient querying.
Pro tip: Store last_message and last_message_time on the Conversation to avoid searching Messages just to display conversation previews in the list.
Expected result: Conversation and Message Data Types are created with all required fields and relationships.
Build the conversation list sidebar
Build the conversation list sidebar
In the Design tab, create a two-column layout: a sidebar on the left (300px wide) and a main chat area on the right. In the sidebar, add a Repeating Group with type Conversation. Set the data source to Do a Search for Conversation where participants contains Current User, sorted by last_message_time descending. Inside each cell, display the other participant's name, the last message preview, and the time. Add a conditional badge showing the count of unread messages: Do a Search for Message where conversation = current cell's Conversation and is_read = no and sender is not Current User, then display the count.
Expected result: A sidebar shows all conversations the current user is part of, sorted by most recent, with unread message badges.
Build the message thread area
Build the message thread area
In the main chat area, add a Repeating Group with type Message. Set the data source to Do a Search for Message where conversation = the selected conversation from the sidebar, sorted by Created Date ascending. Inside each cell, create a Row group. Use conditionals to align messages: When current cell's Message's sender is Current User → align right with a blue background. When sender is not Current User → align left with a gray background. Display the message body, sender name, and timestamp. Below the Repeating Group, add an Input element for typing new messages and a Send button.
Expected result: Messages display in chronological order with the current user's messages on the right and other participants' messages on the left.
Create the send message workflow
Create the send message workflow
Add a workflow: When Send button is clicked (or when the user presses Enter in the input). Add these actions in order: Create a new Message with body = Input's value, sender = Current User, conversation = the selected conversation. Then Make changes to the Conversation: set last_message = Input's value and last_message_time = Current date/time. Finally, clear the input by resetting it. Bubble's auto-updating search on the message Repeating Group will automatically display the new message for all conversation participants without any additional real-time setup.
Expected result: Sending a message creates a Message record, updates the conversation preview, and the message appears in real-time for all participants.
Add typing indicators and auto-scroll
Add typing indicators and auto-scroll
For typing indicators, add a field to Conversation called 'typing_users' (list of Users). When the message Input's value changes, add Current User to the typing_users list. Use a 'Do when condition is true' event to remove the user after 3 seconds of inactivity. Display a '... is typing' text below the message list that shows when typing_users is not empty and does not contain only Current User. For auto-scrolling, use the Scroll to entry action on the Repeating Group after a new message is created, targeting the last entry. Add this as a step in the send message workflow.
Pro tip: Use Schedule a custom event with a 3-second delay to clear the typing indicator rather than a continuously running check, which saves WUs.
Expected result: Users see a typing indicator when the other person is writing, and the message list auto-scrolls to show new messages.
Mark messages as read
Mark messages as read
When a user opens a conversation or the conversation's message list loads, mark all unread messages as read. Create a workflow on the conversation list click event: after setting the selected conversation, add an action to Make changes to a list of things. Target Do a Search for Message where conversation = selected conversation, is_read = no, and sender is not Current User. Set is_read to yes. This updates the unread count badge in the sidebar. Also trigger this workflow when the page loads if a conversation is already selected.
Expected result: Opening a conversation marks all unread messages from other participants as read, clearing the unread badge.
Complete working example
1CHAT SYSTEM DATA MODEL AND WORKFLOWS2=====================================34DATA TYPES:5 Conversation:6 - participants (list of Users)7 - last_message (text)8 - last_message_time (date)9 - name (text, optional for groups)10 - typing_users (list of Users)1112 Message:13 - conversation (Conversation)14 - sender (User)15 - body (text)16 - is_read (yes/no, default: no)17 - Created Date (auto timestamp)1819CONVERSATION LIST:20 Repeating Group: Conversation21 Source: Search Conversations22 where participants contains Current User23 sorted by last_message_time desc24 Cell shows: other user name, last_message,25 last_message_time, unread count badge2627MESSAGE THREAD:28 Repeating Group: Message29 Source: Search Messages30 where conversation = selected conversation31 sorted by Created Date asc32 Cell alignment:33 sender = Current User → right, blue34 sender ≠ Current User → left, gray3536SEND MESSAGE WORKFLOW:37 When Send clicked (or Enter pressed):38 1. Create new Message39 body = Input's value40 sender = Current User41 conversation = selected conversation42 2. Make changes to Conversation43 last_message = Input's value44 last_message_time = Current date/time45 3. Reset Input46 4. Scroll RG to last entry4748MARK AS READ:49 When conversation selected:50 Make changes to list of Messages51 where conversation = selected52 and is_read = no53 and sender ≠ Current User54 → Set is_read = yes5556TYPING INDICATOR:57 Input value changed → Add Current User to58 Conversation's typing_users59 Schedule custom event (3 sec delay) → Remove60 Current User from typing_users61 Display: 'X is typing...' when typing_users62 is not empty and ≠ only Current UserCommon mistakes when building a Chat System in Bubble
Why it's a problem: Searching for messages inside each conversation list cell to get the last message
How to avoid: Store last_message and last_message_time directly on the Conversation record and update them when a new message is sent
Why it's a problem: Not sorting messages by Created Date ascending
How to avoid: Set the message Repeating Group sort to Created Date ascending so oldest messages are at the top and newest at the bottom
Why it's a problem: Forgetting to reset the message input after sending
How to avoid: Add a reset relevant inputs or set input value to empty as the last step in the send workflow
Best practices
- Store conversation metadata like last message on the Conversation record for efficient list display
- Use Bubble's auto-updating searches for real-time message delivery without extra plugins
- Paginate message history to load only the most recent 50 messages initially
- Mark messages as read when the conversation is opened, not when individual messages are displayed
- Use conditional alignment to visually distinguish sent and received messages
- Add timestamps to message groups rather than individual messages for cleaner UI
- Test with multiple browser windows to verify real-time sync between users
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I want to build a real-time one-on-one chat system in my Bubble.io app with message history, unread badges, and typing indicators. What data model should I use and how do I set up real-time updates?
Help me build a messaging feature for my app. I need a conversation list on the left showing recent chats with unread counts, and a message thread on the right that updates in real time.
Frequently asked questions
Does Bubble support real-time chat without plugins?
Yes. Bubble's database searches on page elements auto-update via WebSocket. When a new message is created, it appears automatically in the other user's message list without polling or plugins.
How many messages can a conversation handle?
There is no hard limit, but loading thousands of messages at once will be slow. Paginate the message list to show the most recent 50-100 messages and load more on scroll.
Can I build group chat with this approach?
Yes. The participants field is a list of Users, supporting any number of participants. For group chat, display the conversation name and show sender names on each message.
How do I handle message notifications?
Use a database trigger on Message creation to send push notifications or emails to other conversation participants who are not currently viewing the conversation.
Will the chat system work on mobile?
Yes. Make the layout responsive by hiding the sidebar on mobile and showing a full-screen conversation list that navigates to the message thread.
Can RapidDev help build a chat system for my app?
Yes. RapidDev can build complete messaging systems including group chat, file sharing, typing indicators, and push notifications for your Bubble application.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation