Skip to main content

Chat Firebase Schema

Chat data usually has two responsibilities: load a user's conversation list quickly, and load the message thread for a selected channel. The schema commonly uses a channel document, a message subcollection, and a denormalized chat feed per participant.

Quick Answer

Store channel metadata under channels/{channelID}, messages under channels/{channelID}/thread/{messageID}, and each participant's conversation preview under a per-user chat feed. Keep read receipts, participant ids, last message metadata, and media references synchronized when messages are sent.

Channel Feed

Common per-user feed path:

social_feeds/{userID}/chat_feed/{channelID}

This feed lets the conversations screen load quickly for one user. It usually contains:

{
id: 'channel-id',
title: 'Conversation title',
content: 'Last message preview',
markedAsRead: false,
createdAt: timestamp,
participants: ['user-a', 'user-b'],
participantProfilePictureURLs: ['https://...']
}

Exact names can vary by product. Search the app and backend:

rg "chat_feed|channels|thread|markedAsRead|readUserIDs" src firebase

Channel Metadata

Common channel path:

channels/{channelID}

Typical fields:

{
id: 'channel-id',
creatorID: 'user-id',
name: 'Group name or empty for 1:1',
participants: ['user-a', 'user-b'],
lastMessage: 'Preview text',
lastMessageSenderId: 'user-a',
lastThreadMessageId: 'message-id',
readUserIDs: ['user-a'],
typingUsers: []
}

For one-to-one chats, many apps generate a deterministic channel id from the two participant ids. This prevents duplicate private channels.

Message Thread

Common message path:

channels/{channelID}/thread/{messageID}

Typical message fields:

{
id: 'message-id',
senderID: 'user-a',
senderFirstName: 'Alex',
senderProfilePictureURL: 'https://example.com/avatar.jpg',
content: 'Hello',
createdAt: timestamp,
readUserIDs: ['user-a'],
inReplyToItem: null,
url: null,
mime: null
}

Media messages usually store a url plus a MIME type or media metadata. The file itself should live in Firebase Storage or a CDN, not inside Firestore.

Write Flow

When a message is sent, the app or backend should:

  1. create the message document;
  2. update channel lastMessage metadata;
  3. update each participant's chat feed preview;
  4. update read/unread state;
  5. trigger push notification if enabled;
  6. upload media first if the message contains an attachment.

For large groups or high-volume chat, move fan-out work to backend Functions.

Security Rules

Before production, verify:

  • only channel participants can read channel metadata;
  • only channel participants can read/write messages;
  • users cannot spoof another sender id;
  • message media paths are scoped to channel or sender ownership;
  • blocked users cannot continue sending messages;
  • admin moderation has explicit server-side controls.

Verification Checklist

  • User conversations screen loads from the per-user chat feed.
  • Opening a conversation loads messages from the channel thread.
  • Sending text updates both message thread and conversation previews.
  • Sending media uploads the file and stores a valid URL.
  • Read receipts update for the current user only.
  • Push notifications fire only for intended recipients.
  • Block/report behavior is enforced.

Troubleshooting

ProblemFix
Conversations screen loads foreverCheck chat feed query, Auth state, rules, and loading/error handling.
Message sends but preview does not updateCheck channel metadata and per-user chat feed fan-out.
Media message does not renderCheck Storage upload, URL, MIME type, and read rules.
Duplicate private chats appearUse deterministic 1:1 channel ids.
User sees another user's chatTighten participant checks in Firestore rules.