# How to Build a Content Approval Workflow in FlutterFlow

- Tool: FlutterFlow
- Difficulty: Beginner
- Time required: 25-35 min
- Compatibility: FlutterFlow Pro+ (Cloud Functions for notifications)
- Last updated: March 2026

## TL;DR

Build a multi-stage content approval workflow using Firestore status fields (draft, submitted, inReview, approved, published, rejected). Authors submit content, reviewers approve or reject with comments, and admins override when needed. Track every status change in a workflow_events subcollection. Use Conditional Visibility and role checks to enforce who can perform each action. Email notifications fire on every status transition via Cloud Functions.

## Building an Editorial Approval Pipeline in FlutterFlow

Content-heavy apps need quality gates before publishing. This tutorial builds a full editorial workflow where authors draft and submit, reviewers approve or reject with feedback, and every transition is logged. Role-based controls ensure only authorized users can move content through each stage.

## Before you start

- FlutterFlow project with Firebase authentication configured
- Firestore collections for content and users
- Users collection with a role field (author, reviewer, admin)
- Basic understanding of Conditional Visibility in FlutterFlow

## Step-by-step guide

### 1. Create the Firestore schema for content and workflow events

In Firestore, create a content collection with fields: title (String), body (String), authorId (String), workflowStatus (String, default 'draft'), reviewerId (String, optional), reviewComment (String, optional), createdAt (Timestamp), updatedAt (Timestamp). Then add a subcollection workflow_events under each content document with fields: action (String), userId (String), comment (String), timestamp (Timestamp). The status field drives the entire workflow: draft, submitted, inReview, approved, published, or rejected.

**Expected result:** Firestore has a content collection with status tracking and a workflow_events subcollection for audit history.

### 2. Build the author submission view with status badges

Create a page called MyContent. Add a ListView bound to a Backend Query on the content collection filtered by authorId equals current user. Each list item shows: title Text, a Container styled as a status badge (green for published, yellow for inReview, red for rejected, grey for draft). Use Conditional Visibility on a Submit button so it only appears when workflowStatus equals 'draft'. The Submit button runs an Action Flow: Update Document to set workflowStatus to 'submitted' and updatedAt to now, then creates a workflow_events doc with action 'submitted'.

**Expected result:** Authors see their content list with color-coded status badges and can submit draft items for review.

### 3. Build the reviewer queue with approve and reject actions

Create a ReviewQueue page visible only to users with role 'reviewer' or 'admin'. Add a ListView querying content where workflowStatus equals 'submitted' or 'inReview', ordered by updatedAt ascending. Each item shows the title, author name, and submission date. Add two buttons: Approve and Reject. Approve runs an Action Flow that updates workflowStatus to 'approved', sets reviewerId, and creates a workflow_events doc. Reject opens a dialog with a TextField for reviewer comments, then updates status to 'rejected' with the comment stored on both the content doc and the workflow_events entry.

**Expected result:** Reviewers see a queue of submitted content and can approve or reject each item with optional comments.

### 4. Display the workflow history timeline

On the content detail page, add a section below the main content showing the workflow history. Query the workflow_events subcollection ordered by timestamp descending. Display each event in a Column: a Row with the user display name and a formatted timestamp, below it the action text (styled bold) and any comment. Use a vertical line or Container border on the left side to create a timeline visual effect. This gives authors and reviewers full visibility into every status transition and who performed it.

**Expected result:** A chronological timeline shows every workflow action with the actor name, timestamp, and any reviewer comments.

### 5. Lock editing for content not in draft status

On the content edit page, wrap all editable fields (title TextField, body TextField) with Conditional Visibility set to show only when workflowStatus equals 'draft'. When status is anything else, show read-only Text widgets instead. Add a Request Return to Draft button visible only when status is 'rejected', which sets the status back to 'draft' and logs a workflow event. This prevents authors from editing content that is actively being reviewed, which would cause reviewers to see different content than what was submitted.

**Expected result:** Content fields are locked for editing unless the item is in draft status, preventing mid-review modifications.

### 6. Add Cloud Function notifications on status changes

Deploy a Cloud Function triggered by Firestore onUpdate on the content collection. When workflowStatus changes, the function determines the recipient: if status changed to 'submitted', email the reviewer team; if changed to 'approved' or 'rejected', email the author. Include the content title, new status, and any reviewer comment in the email body. Use a mail-sending service like SendGrid or Firebase Extensions for email delivery. This keeps all stakeholders informed without requiring them to check the app constantly.

```
// Cloud Function: onContentStatusChange
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();

exports.onContentStatusChange = functions.firestore
  .document('content/{contentId}')
  .onUpdate(async (change, context) => {
    const before = change.before.data();
    const after = change.after.data();
    if (before.workflowStatus === after.workflowStatus) return;
    
    const contentTitle = after.title;
    const newStatus = after.workflowStatus;
    const comment = after.reviewComment || '';
    
    // Determine recipient based on new status
    let recipientId;
    if (newStatus === 'submitted') {
      // Notify reviewers (query users with role 'reviewer')
      const reviewers = await admin.firestore()
        .collection('users')
        .where('role', '==', 'reviewer').get();
      // Send to each reviewer
    } else {
      recipientId = after.authorId;
    }
    // Send email via SendGrid or Firebase Extension
  });
```

**Expected result:** Email notifications are sent automatically when content moves between workflow stages.

## Complete code example

File: `FlutterFlow Content Approval Workflow`

```text
FIRESTORE SCHEMA:
  content (collection):
    title: String
    body: String
    authorId: String
    workflowStatus: String (draft|submitted|inReview|approved|published|rejected)
    reviewerId: String (optional)
    reviewComment: String (optional)
    createdAt: Timestamp
    updatedAt: Timestamp
  content/{id}/workflow_events (subcollection):
    action: String
    userId: String
    comment: String
    timestamp: Timestamp

PAGE: MyContent (Author View)
  ListView → Backend Query: content where authorId == currentUser
    Container (status badge: color by workflowStatus)
    Text (title)
    Button "Submit" → Conditional Visibility: status == 'draft'
      Action: Update Document → workflowStatus = 'submitted'
      Action: Create workflow_events doc

PAGE: ReviewQueue (Reviewer View)
  Conditional Access: currentUser.role == 'reviewer' OR 'admin'
  ListView → Backend Query: content where status IN ['submitted','inReview']
    Text (title + author + date)
    Button "Approve" → Update status to 'approved' + log event
    Button "Reject" → Show Dialog with comment TextField
      → Update status to 'rejected' + store comment + log event

PAGE: ContentDetail
  Content display (title, body, status badge)
  Workflow Timeline section:
    ListView → workflow_events orderBy timestamp desc
      Row: userName + formatted timestamp
      Text: action (bold) + comment

EDIT MODE:
  Conditional Visibility: fields editable only when status == 'draft'
  Button "Return to Draft" visible when status == 'rejected'

CLOUD FUNCTION:
  onUpdate content → if workflowStatus changed → email notification
```

## Common mistakes

- **Allowing authors to edit content that is already in review** — The reviewer sees different content than what was submitted. Approvals become meaningless because the published version differs from what was reviewed. Fix: Lock editing when workflowStatus is anything other than 'draft'. Authors must request a return to draft status before making changes.
- **Only hiding UI buttons without Firestore Security Rules enforcement** — Users can bypass the FlutterFlow UI and call Firestore directly via API. An author could change their own content status to 'published' without review. Fix: Add Firestore Security Rules that check the user's role field before allowing status field updates. Only reviewers and admins can set status to approved or published.
- **Not logging workflow events on every status transition** — Without an audit trail, disputes about who approved what and when become unresolvable. There is no accountability for editorial decisions. Fix: Create a workflow_events subcollection document on every status change, recording the action, userId, comment, and timestamp.
- **Sending notifications synchronously in the Action Flow** — Email delivery takes several seconds and can fail, blocking the user's action. If the email fails, the status update may also fail or leave the UI in an inconsistent state. Fix: Use a Cloud Function triggered by Firestore onUpdate to send notifications asynchronously. The status update completes immediately and the notification fires in the background.

## Best practices

- Lock content editing when status is not draft to prevent mid-review modifications
- Enforce status transitions in Firestore Security Rules, not just the UI
- Log every workflow event in a subcollection for a complete audit trail
- Color-code status badges so users can scan content state at a glance
- Send notifications via Cloud Functions so status updates are never blocked by email delivery
- Include reviewer comments on rejection so authors know exactly what to fix
- Add a Return to Draft action for rejected content so authors can revise and resubmit

## Frequently asked questions

### Can I add more than two reviewer levels?

Yes. Add intermediate statuses like inReview1 and inReview2. Each level queries content at its specific status, and approval advances it to the next level. This creates a multi-tier approval chain.

### How do I prevent the same reviewer from approving their own content?

In the Approve action, add a condition: if content.authorId equals currentUser.uid, show a SnackBar saying you cannot review your own content. Also enforce this in Firestore Security Rules.

### Can rejected content be resubmitted?

Yes. Add a Return to Draft button visible only when status is rejected. This sets the status back to draft so the author can edit and resubmit for another review cycle.

### How do I track how long content stays in each stage?

The workflow_events subcollection records timestamps for each transition. Calculate duration by subtracting consecutive event timestamps. Display average review time on an admin dashboard.

### Can I add automatic approval for certain content types?

Yes. In the Cloud Function triggered on status change to submitted, check the content type or author trust level. If criteria are met, automatically update the status to approved without manual review.

### Can RapidDev help build complex approval workflows?

Yes. RapidDev can build multi-tier approval systems with custom business rules, automated routing, SLA tracking, and integration with external review tools.

---

Source: https://www.rapidevelopers.com/flutterflow-tutorials/how-to-build-a-content-approval-workflow-in-flutterflow
© RapidDev — https://www.rapidevelopers.com/flutterflow-tutorials/how-to-build-a-content-approval-workflow-in-flutterflow
