Skip to main content
RapidDev - Software Development Agency
flutterflow-tutorials

How to Set Up a Community Forum or Discussion Board in FlutterFlow

Build a community forum using Firestore collections for categories, threads, and a replies subcollection. Display categories with thread count badges in a ListView. Thread lists sort by lastReplyAt descending with pinned threads at top. Each thread detail page shows the original post plus a replies ListView with upvote/downvote buttons. Denormalize replyCount on the thread document so the list can display '23 replies' without querying the entire subcollection.

What you'll learn

  • How to model categories, threads, and replies in Firestore with proper denormalization
  • How to display a category list with thread count badges and a sortable thread list
  • How to build threaded discussions with a replies subcollection and upvote system
  • How to implement pinned threads and reply count denormalization
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner9 min read25-35 minFlutterFlow Free+March 2026RapidDev Engineering Team
TL;DR

Build a community forum using Firestore collections for categories, threads, and a replies subcollection. Display categories with thread count badges in a ListView. Thread lists sort by lastReplyAt descending with pinned threads at top. Each thread detail page shows the original post plus a replies ListView with upvote/downvote buttons. Denormalize replyCount on the thread document so the list can display '23 replies' without querying the entire subcollection.

Forum with categories, threaded replies, upvotes, and pinned threads

A community forum organizes discussions by topic categories. This tutorial builds the full structure: a categories page listing forum sections with thread counts, a thread list page sorted by most recent activity with pinned threads at top, and a thread detail page with the original post and a replies subcollection. Users can reply, upvote/downvote other replies, and admins can pin or lock threads. Unlike real-time chat (which is peer-to-peer messaging), a forum is public threaded discussions organized by categories.

Prerequisites

  • A FlutterFlow project with Firebase/Firestore connected
  • Firebase Authentication enabled with user accounts
  • Basic understanding of Firestore subcollections and Backend Queries
  • Familiarity with ListView and Conditional Visibility in FlutterFlow

Step-by-step guide

1

Create the Firestore categories, threads, and replies collections

Create a categories collection with fields: name (String), description (String), icon (String — icon name like 'chat_bubble' or emoji), order (Integer for display sorting), and threadCount (Integer — denormalized, incremented on new thread). Create a threads collection with fields: categoryId (String), authorId (String), authorName (String — denormalized), title (String), body (String), replyCount (Integer — denormalized), lastReplyAt (Timestamp), isPinned (Boolean, default false), isLocked (Boolean, default false), createdAt (Timestamp), and upvotes (Integer, default 0). Create a subcollection threads/{id}/replies with: authorId (String), authorName (String), body (String), timestamp (Timestamp), and upvotes (Integer, default 0). Add 3-4 test categories (General, Help, Feature Requests, Off-Topic) with a few threads and replies each.

Expected result: Firestore has categories, threads, and replies subcollections with test data and denormalized counts.

2

Build the categories page with thread count badges

Create a CategoriesPage with a ListView bound to a Backend Query on categories ordered by order ascending. Each list item is a Container with a Row: an Icon widget (mapped from the icon field via a Custom Function or hardcoded per category), a Column with the category name in bodyLarge bold and description in bodySmall secondary color, and a Badge Container (circular, primary color) showing threadCount. On Tap, navigate to ThreadListPage passing the categoryId and category name as parameters. Add an AppBar with the forum name and a search IconButton that navigates to a SearchPage for finding threads across all categories.

Expected result: A categorized forum home page shows all categories with descriptions and thread count badges.

3

Create the thread list with pinned threads and sorting

Create a ThreadListPage that receives categoryId as a parameter. Add a ListView bound to a Backend Query on threads where categoryId == parameter, ordered by isPinned descending then lastReplyAt descending. This puts pinned threads at the top and sorts the rest by most recent activity. Each thread row shows: a pin Icon (Conditional Visibility: isPinned == true), the title in bodyLarge bold, a Row with authorName + relative time from createdAt, a Row showing a reply icon with replyCount text and an upvote icon with upvotes count, and a lock Icon if isLocked == true. On Tap, navigate to ThreadDetailPage with the thread document reference. Add a FAB for creating a new thread: opens a page with TextField for title, TextField (multiline) for body, and a Post button that creates the thread document and increments the category's threadCount using FieldValue.increment(1).

Expected result: Thread list shows pinned threads first, then others sorted by most recent reply. Each thread displays reply count and activity indicators.

4

Build the thread detail page with replies and upvoting

Create ThreadDetailPage receiving the thread document reference. At the top, display the original post: title in headlineSmall, author info Row (CircleImage avatar + authorName + relative time), and body Text. Below, add a Divider and a Text showing 'X Replies'. Then a ListView bound to the replies subcollection ordered by timestamp ascending. Each reply is a Container with: author info Row (avatar + name + relative time), body Text, and an upvote/downvote Row — IconButton thumb_up that increments the reply's upvotes field, a Text showing the count, and IconButton thumb_down that decrements. To prevent duplicate votes, create a subcollection replies/{id}/voters with voter UID documents. Check if current user exists in voters before allowing the vote. If the thread is not locked, show a reply input at the bottom: TextField (multiline) + Send IconButton. The send action creates a reply document and updates the thread's replyCount (FieldValue.increment(1)) and lastReplyAt to now.

Expected result: Thread detail shows the original post and all replies with upvote/downvote counts. New replies update the thread's replyCount and lastReplyAt.

5

Add admin controls for pinning, locking, and moderating threads

For admin users (check currentUserDocument.role == admin), add a PopupMenuButton on each thread in the list and on the thread detail page with options: Pin/Unpin Thread (toggles isPinned boolean), Lock/Unlock Thread (toggles isLocked — locked threads hide the reply input), and Delete Thread (sets a deleted boolean or removes the document, decrements category threadCount). On the thread detail page, add a Report button for regular users that creates a document in a reports collection with threadId, reporterId, reason, and timestamp for admin review. When a thread is locked, show a banner: 'This thread is locked. No new replies can be posted.' and hide the reply TextField using Conditional Visibility on isLocked == false.

Expected result: Admins can pin, lock, and delete threads. Regular users can report threads. Locked threads prevent new replies.

Complete working example

Community Forum Architecture
1Firestore Data Model:
2 categories/{categoryId}
3 name: String ("General Discussion")
4 description: String ("Talk about anything")
5 icon: String ("chat_bubble")
6 order: Integer (1)
7 threadCount: Integer (42) [denormalized]
8 threads/{threadId}
9 categoryId: String
10 authorId: String
11 authorName: String (denormalized)
12 title: String ("Best practices for...")
13 body: String (original post content)
14 replyCount: Integer (23) [denormalized]
15 lastReplyAt: Timestamp [denormalized]
16 isPinned: Boolean (false)
17 isLocked: Boolean (false)
18 createdAt: Timestamp
19 upvotes: Integer (5)
20 threads/{threadId}/replies/{replyId}
21 authorId: String
22 authorName: String (denormalized)
23 body: String
24 timestamp: Timestamp
25 upvotes: Integer (3)
26
27CategoriesPage:
28 AppBar: "Community Forum" + search icon
29 ListView (query: categories, orderBy: order ASC)
30 Container (per category)
31 Row
32 Icon (category icon)
33 Column: Text(name, bold) + Text(description)
34 Badge (threadCount)
35 On Tap ThreadListPage(categoryId)
36
37ThreadListPage:
38 AppBar: category name
39 ListView (query: threads where categoryId==param,
40 orderBy: isPinned DESC, lastReplyAt DESC)
41 Container (per thread)
42 Column
43 Row: Pin icon [if pinned] + Text(title, bold)
44 Row: Text(authorName) + Text(timeAgo)
45 Row: Icon(reply) Text(replyCount) + Icon(up) Text(upvotes)
46 + Lock icon [if locked]
47 FAB New Thread Form
48
49ThreadDetailPage:
50 Original Post: title + author + body
51 Divider + Text("23 Replies")
52 ListView (query: replies, orderBy: timestamp ASC)
53 Container (per reply)
54 Row: avatar + authorName + timeAgo
55 Text (body)
56 Row: thumb_up(+1) + count + thumb_down(-1)
57 TextField + Send [Cond. Vis: !isLocked]

Common mistakes

Why it's a problem: Not denormalizing replyCount on the thread document

How to avoid: Store replyCount as a field on the thread document. When a new reply is created, increment it using FieldValue.increment(1). This way, the thread list only reads thread documents (one read each) and displays the count without touching the replies subcollection.

Why it's a problem: Sorting threads by createdAt instead of lastReplyAt

How to avoid: Sort by lastReplyAt descending. Update this timestamp on the thread document every time a new reply is posted. Active discussions naturally float to the top, like Reddit or traditional forums.

Why it's a problem: Allowing unlimited upvotes per user on a single reply

How to avoid: Create a voters subcollection under each reply (or a votes collection with a composite document ID: replyId_userId). Before allowing a vote, check if the user has already voted. If they have, either ignore the tap or toggle the vote off.

Best practices

  • Denormalize replyCount and lastReplyAt on thread documents to avoid expensive subcollection queries in list views
  • Sort threads by isPinned descending then lastReplyAt descending for proper forum ordering
  • Create composite Firestore indexes for the thread list query (categoryId + isPinned + lastReplyAt)
  • Track voters per reply to prevent duplicate upvotes — use a subcollection or composite document ID
  • Use Conditional Visibility to hide the reply input when a thread is locked (isLocked == true)
  • Increment category threadCount atomically with FieldValue.increment when new threads are created
  • Add pagination (limit 20 + infinite scroll) on both thread lists and reply lists for scalability

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

Design a Firestore data model for a community forum with categories, threads, and a replies subcollection. Include denormalized fields for replyCount, lastReplyAt, and threadCount. Write Firestore Security Rules that allow authenticated users to create threads and replies, but only admins can pin, lock, or delete threads.

FlutterFlow Prompt

Create a forum thread list page with a ListView showing thread titles, author names, reply counts, and time since last activity. Pinned threads should appear at the top with a pin icon. Add a floating action button to create a new thread with a title and body text field.

Frequently asked questions

What is the difference between a forum and a chat application?

A forum is organized by categories and topics with threaded replies that persist indefinitely. Anyone can browse past discussions. A chat application is real-time messaging between specific users or groups, with conversations flowing chronologically. Forums are for community knowledge sharing; chat is for direct communication.

How do I implement search across all threads and replies?

For basic search, use a Firestore prefix query on thread titles: where title >= searchTerm AND title <= searchTerm + '\uf8ff'. For full-text search across thread bodies and replies, integrate Algolia or Typesense via a Cloud Function that indexes content on write.

How do I handle threads with hundreds of replies?

Add pagination to the replies ListView: limit to 20 replies per query with infinite scroll enabled. Users scroll to load more. For very long threads, add a 'Jump to Latest' button that scrolls to the bottom of the reversed list.

Can I add rich text formatting to thread bodies and replies?

FlutterFlow's Text widget shows plain text. For rich formatting, store content as Markdown and render with a Custom Widget using the flutter_markdown package, or use a RichText widget with parsed TextSpans. This requires FlutterFlow Pro for custom code.

How do I notify users when someone replies to their thread?

When a reply is created, trigger a Cloud Function that reads the thread's authorId and creates a notification document in users/{authorId}/notifications with the reply details. Use FCM push notifications via the Cloud Function to alert users even when the app is closed.

Can RapidDev help build a production forum with moderation tools and analytics?

Yes. A production forum needs content moderation (automated and manual), spam detection, user reputation systems, notification digests, SEO-friendly rendering, and admin analytics dashboards. RapidDev can architect the complete community platform.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.