# How to Build a Chat System in Bubble

- Tool: Bubble
- Difficulty: Beginner
- Time required: 30-40 min
- Compatibility: All Bubble plans
- Last updated: March 2026

## TL;DR

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.

## Before you start

- 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

### 1. 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.

### 2. 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.

### 3. 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.

### 4. 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.

### 5. 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.

### 6. 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 code example

File: `Workflow summary`

```text
CHAT SYSTEM DATA MODEL AND WORKFLOWS
=====================================

DATA TYPES:
  Conversation:
    - participants (list of Users)
    - last_message (text)
    - last_message_time (date)
    - name (text, optional for groups)
    - typing_users (list of Users)

  Message:
    - conversation (Conversation)
    - sender (User)
    - body (text)
    - is_read (yes/no, default: no)
    - Created Date (auto timestamp)

CONVERSATION LIST:
  Repeating Group: Conversation
  Source: Search Conversations
    where participants contains Current User
    sorted by last_message_time desc
  Cell shows: other user name, last_message,
    last_message_time, unread count badge

MESSAGE THREAD:
  Repeating Group: Message
  Source: Search Messages
    where conversation = selected conversation
    sorted by Created Date asc
  Cell alignment:
    sender = Current User → right, blue
    sender ≠ Current User → left, gray

SEND MESSAGE WORKFLOW:
  When Send clicked (or Enter pressed):
    1. Create new Message
      body = Input's value
      sender = Current User
      conversation = selected conversation
    2. Make changes to Conversation
      last_message = Input's value
      last_message_time = Current date/time
    3. Reset Input
    4. Scroll RG to last entry

MARK AS READ:
  When conversation selected:
    Make changes to list of Messages
      where conversation = selected
      and is_read = no
      and sender ≠ Current User
    → Set is_read = yes

TYPING INDICATOR:
  Input value changed → Add Current User to
    Conversation's typing_users
  Schedule custom event (3 sec delay) → Remove
    Current User from typing_users
  Display: 'X is typing...' when typing_users
    is not empty and ≠ only Current User
```

## Common mistakes

- **Searching for messages inside each conversation list cell to get the last message** — Running a search per conversation cell multiplies queries and slows down the conversation list dramatically Fix: Store last_message and last_message_time directly on the Conversation record and update them when a new message is sent
- **Not sorting messages by Created Date ascending** — Messages appear in random or reverse order, making the conversation unreadable Fix: Set the message Repeating Group sort to Created Date ascending so oldest messages are at the top and newest at the bottom
- **Forgetting to reset the message input after sending** — The previous message text stays in the input field, confusing users who may accidentally send duplicates Fix: 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

## 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.

---

Source: https://www.rapidevelopers.com/bubble-tutorial/set-up-a-chat-system-in-bubble
© RapidDev — https://www.rapidevelopers.com/bubble-tutorial/set-up-a-chat-system-in-bubble
