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.
Prerequisites
- 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
Create the Firestore schema for content and workflow events
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.
Build the author submission view with status badges
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.
Build the reviewer queue with approve and reject actions
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.
Display the workflow history timeline
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.
Lock editing for content not in draft status
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.
Add Cloud Function notifications on status changes
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.
1// Cloud Function: onContentStatusChange2const functions = require('firebase-functions');3const admin = require('firebase-admin');4admin.initializeApp();56exports.onContentStatusChange = functions.firestore7 .document('content/{contentId}')8 .onUpdate(async (change, context) => {9 const before = change.before.data();10 const after = change.after.data();11 if (before.workflowStatus === after.workflowStatus) return;12 13 const contentTitle = after.title;14 const newStatus = after.workflowStatus;15 const comment = after.reviewComment || '';16 17 // Determine recipient based on new status18 let recipientId;19 if (newStatus === 'submitted') {20 // Notify reviewers (query users with role 'reviewer')21 const reviewers = await admin.firestore()22 .collection('users')23 .where('role', '==', 'reviewer').get();24 // Send to each reviewer25 } else {26 recipientId = after.authorId;27 }28 // Send email via SendGrid or Firebase Extension29 });Expected result: Email notifications are sent automatically when content moves between workflow stages.
Complete working example
1FIRESTORE SCHEMA:2 content (collection):3 title: String4 body: String5 authorId: String6 workflowStatus: String (draft|submitted|inReview|approved|published|rejected)7 reviewerId: String (optional)8 reviewComment: String (optional)9 createdAt: Timestamp10 updatedAt: Timestamp11 content/{id}/workflow_events (subcollection):12 action: String13 userId: String14 comment: String15 timestamp: Timestamp1617PAGE: MyContent (Author View)18 ListView → Backend Query: content where authorId == currentUser19 Container (status badge: color by workflowStatus)20 Text (title)21 Button "Submit" → Conditional Visibility: status == 'draft'22 Action: Update Document → workflowStatus = 'submitted'23 Action: Create workflow_events doc2425PAGE: ReviewQueue (Reviewer View)26 Conditional Access: currentUser.role == 'reviewer' OR 'admin'27 ListView → Backend Query: content where status IN ['submitted','inReview']28 Text (title + author + date)29 Button "Approve" → Update status to 'approved' + log event30 Button "Reject" → Show Dialog with comment TextField31 → Update status to 'rejected' + store comment + log event3233PAGE: ContentDetail34 Content display (title, body, status badge)35 Workflow Timeline section:36 ListView → workflow_events orderBy timestamp desc37 Row: userName + formatted timestamp38 Text: action (bold) + comment3940EDIT MODE:41 Conditional Visibility: fields editable only when status == 'draft'42 Button "Return to Draft" visible when status == 'rejected'4344CLOUD FUNCTION:45 onUpdate content → if workflowStatus changed → email notificationCommon mistakes when building a Content Approval Workflow in FlutterFlow
Why it's a problem: Allowing authors to edit content that is already in review
How to avoid: Lock editing when workflowStatus is anything other than 'draft'. Authors must request a return to draft status before making changes.
Why it's a problem: Only hiding UI buttons without Firestore Security Rules enforcement
How to avoid: 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.
Why it's a problem: Not logging workflow events on every status transition
How to avoid: Create a workflow_events subcollection document on every status change, recording the action, userId, comment, and timestamp.
Why it's a problem: Sending notifications synchronously in the Action Flow
How to avoid: 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
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
Design a content approval workflow for a FlutterFlow app. I need Firestore schema for content with workflowStatus (draft/submitted/inReview/approved/published/rejected), a workflow_events subcollection for audit logs, role-based access for authors and reviewers, and Cloud Functions for email notifications on status changes.
Create a page called ReviewQueue with a ListView showing content documents where workflowStatus is 'submitted'. Each item has the title, author name, and two buttons: Approve and Reject. Approve updates the status to approved. Reject opens a dialog with a comment field then updates status to rejected.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation