Secure your FlutterFlow Firestore database by writing security rules that enforce authentication (request.auth != null), owner-only access (request.auth.uid == resource.data.userId), and role-based access (checking a role field on the user's document). Never leave allow read, write: if true in production. Deploy rules from FlutterFlow's Firestore panel or Firebase Console and test them with the Rules Playground before going live.
Firestore rules are your last line of defense against data breaches
FlutterFlow apps run entirely on the client device — anyone with a packet sniffer or a decompiled app can see your Firestore collection paths and document structures. Without security rules, anyone who finds your Firebase config can read, write, or delete your entire database. Firestore security rules are server-side policies enforced by Google's infrastructure, not by your app code. No rule bypass is possible from the client. This tutorial walks through writing production-ready security rules for the most common FlutterFlow app patterns: authenticated-only access, owner access, admin roles, and input validation. Rules are deployed from FlutterFlow or Firebase Console — no app resubmission required.
Prerequisites
- FlutterFlow project with Firebase Firestore connected
- Firebase Authentication enabled with at least one sign-in method configured
- Basic understanding of your Firestore collection structure (collection names, key field names)
- FlutterFlow Firestore panel access to view and edit security rules
Step-by-step guide
Replace test mode rules with authenticated-only access
Replace test mode rules with authenticated-only access
When you create a Firestore database in Firebase Console, the default test mode rules are: allow read, write: if request.time < timestamp.date(YEAR, MM, DD). These expire after 30 days and expose your database to any authenticated or unauthenticated request until then. In FlutterFlow, go to the Firestore panel (left sidebar) → click the Rules tab → you will see the current rules. Replace the entire rules block with a base rule that requires authentication for all operations. This single change blocks all unauthenticated API access to your database. Deploy the rules by clicking the Deploy Rules button in FlutterFlow's Firestore panel, or paste the rules into Firebase Console → Firestore Database → Rules and click Publish.
1// BEFORE: Test mode (INSECURE)2rules_version = '2';3service cloud.firestore {4 match /databases/{database}/documents {5 match /{document=**} {6 allow read, write: if true;7 }8 }9}Expected result: All unauthenticated requests to Firestore are rejected with permission-denied errors.
Add owner-only access rules for user data
Add owner-only access rules for user data
The baseline rule lets any authenticated user read or write any document. For collections containing user data (profiles, orders, messages), you need owner-only rules that check the requesting user's UID matches the document's owner field. In your users collection, the document ID should be the Firebase UID (FlutterFlow sets this automatically when you use Create Document with Current User UID as the document ID). Write a rule matching users/{userId} that allows read and write only when the request.auth.uid equals the userId path parameter. For other collections (like orders), the document has a userId field — compare request.auth.uid to resource.data.userId for reads and request.auth.uid to request.resource.data.userId for writes.
1rules_version = '2';2service cloud.firestore {3 match /databases/{database}/documents {45 // Users: only the owner can read/write their profile6 match /users/{userId} {7 allow read, write: if request.auth != null8 && request.auth.uid == userId;9 }1011 // Orders: owner reads, owner creates (userId must match)12 match /orders/{orderId} {13 allow read: if request.auth != null14 && request.auth.uid == resource.data.userId;15 allow create: if request.auth != null16 && request.auth.uid == request.resource.data.userId;17 allow update, delete: if false; // orders are immutable18 }1920 // Posts: any authenticated user can read,21 // only owner can write22 match /posts/{postId} {23 allow read: if request.auth != null;24 allow write: if request.auth != null25 && request.auth.uid == resource.data.authorId;26 }27 }28}Expected result: Users can only access their own data; attempting to read another user's orders or profile returns permission-denied.
Implement role-based access for admin functionality
Implement role-based access for admin functionality
For admin dashboards or moderation tools, you need role-based access. The cleanest Firestore pattern stores the user's role in their users/{uid} document as a role field (String: 'user', 'admin', 'moderator'). The security rule fetches the requesting user's role document and checks the field. Use the get() function in rules: get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin'. This performs a server-side document read during rule evaluation. Note: each get() in rules counts as one Firestore read operation against your billing quota. For performance-sensitive apps with high traffic, consider using Firebase Custom Claims instead (set via Admin SDK in Cloud Functions) which embed the role in the JWT token and avoid the extra read.
1// Role-based access with Firestore user documents2rules_version = '2';3service cloud.firestore {4 match /databases/{database}/documents {56 // Helper function to get user role7 function getUserRole() {8 return get(/databases/$(database)/documents/9 users/$(request.auth.uid)).data.get('role', 'user');10 }1112 // Admin-only: full access to analytics collection13 match /analytics/{doc} {14 allow read, write: if request.auth != null15 && getUserRole() == 'admin';16 }1718 // Moderators and admins can delete flagged content19 match /posts/{postId} {20 allow read: if request.auth != null;21 allow create: if request.auth != null;22 allow update: if request.auth != null23 && request.auth.uid == resource.data.authorId;24 allow delete: if request.auth != null25 && (getUserRole() == 'admin'26 || getUserRole() == 'moderator');27 }28 }29}Expected result: Admin and moderator users can perform privileged operations while regular users are restricted.
Add field validation to prevent malformed data
Add field validation to prevent malformed data
Security rules can validate incoming data to prevent users from writing invalid or malicious content. Check that required fields are present and have the correct type using the is operator and size() function. For example, prevent users from submitting a post with a title longer than 200 characters, or with a price field that is negative. Add validation conditions to your allow create and allow update rules using request.resource.data (the new data being written). Combine multiple conditions with &&. If any condition is false, the write is rejected server-side.
1// Field validation in security rules2match /posts/{postId} {3 allow create: if request.auth != null4 // Must be the author5 && request.auth.uid == request.resource.data.authorId6 // Title must be a non-empty string under 200 chars7 && request.resource.data.title is string8 && request.resource.data.title.size() > 09 && request.resource.data.title.size() <= 20010 // Body must be a string under 5000 chars11 && request.resource.data.body is string12 && request.resource.data.body.size() <= 500013 // Category must be one of the allowed values14 && request.resource.data.category in15 ['news', 'tips', 'question', 'announcement']16 // No extra fields allowed (prevents injection)17 && request.resource.data.keys().hasOnly(18 ['authorId', 'title', 'body', 'category',19 'createdAt', 'updatedAt']);20}Expected result: Invalid writes (oversized fields, wrong types, disallowed categories) are rejected before reaching the database.
Test rules with the Firebase Console Rules Playground
Test rules with the Firebase Console Rules Playground
Before deploying rules to production, test them in Firebase Console → Firestore Database → Rules → click the Rules Playground button at the top right. Select the operation (get, list, create, update, delete), enter a document path (e.g., users/abc123), and configure the authentication context (authenticated user with a specific UID, or unauthenticated). Click Run to see whether the operation is Allowed or Denied and which rule matched. Test every scenario: authenticated owner, authenticated non-owner, unauthenticated, admin user, invalid data. A common mistake is writing a rule that accidentally allows more access than intended — the playground catches this before it reaches users.
Expected result: All rule scenarios pass the playground tests: allowed operations succeed and blocked operations are correctly denied.
Complete working example
1rules_version = '2';2service cloud.firestore {3 match /databases/{database}/documents {45 // Helper: get current user's role6 function getRole() {7 return get(/databases/$(database)/documents/8 users/$(request.auth.uid)).data.get('role','user');9 }1011 // Helper: check if user is authenticated12 function isAuth() {13 return request.auth != null;14 }1516 // Helper: check if user owns the document17 function isOwner(ownerField) {18 return isAuth()19 && request.auth.uid == resource.data[ownerField];20 }2122 // Users collection23 match /users/{userId} {24 allow read: if isAuth()25 && (request.auth.uid == userId26 || getRole() == 'admin');27 allow create: if isAuth()28 && request.auth.uid == userId;29 allow update: if isAuth()30 && request.auth.uid == userId31 && !request.resource.data.diff(resource.data)32 .affectedKeys().hasAny(['role','createdAt']);33 allow delete: if false;34 }3536 // Posts collection37 match /posts/{postId} {38 allow read: if isAuth();39 allow create: if isAuth()40 && request.resource.data.authorId41 == request.auth.uid42 && request.resource.data.title.size() <= 200;43 allow update: if isOwner('authorId');44 allow delete: if isOwner('authorId')45 || getRole() == 'admin';46 }4748 // Orders collection49 match /orders/{orderId} {50 allow read: if isOwner('userId')51 || getRole() == 'admin';52 allow create: if isAuth()53 && request.resource.data.userId54 == request.auth.uid;55 allow update, delete: if false;56 }57 }58}Common mistakes when securing Your FlutterFlow Database with Firestore Rules
Why it's a problem: Leaving allow read, write: if true rules beyond the 30-day test period
How to avoid: Write proper security rules before launch. Use the authenticated-only baseline from Step 1 as the minimum, then add per-collection rules. Test with the Rules Playground before deploying.
Why it's a problem: Using resource.data in allow create rules (checking the document that does not yet exist)
How to avoid: For create rules, use request.resource.data (the data being written in the request). For update and delete rules, use resource.data (the existing document). For read rules, use resource.data (the document being read).
Why it's a problem: Writing overly permissive wildcard rules that grant access to all subcollections
How to avoid: Write explicit rules for each subcollection with the specific permissions needed. Only use the wildcard {document=**} when you truly want the rule to apply to all subcollections, and audit what that includes.
Best practices
- Deploy security rules before going live — never wait until after launch to secure your database
- Use the Rules Playground to test every access pattern your app uses, including edge cases like unauthenticated users and cross-user access attempts
- Store user roles in Firestore user documents and protect the role field from user modification with rules that deny role changes from the client
- Separate read and write permissions explicitly — public read + authenticated write is a common pattern (news feeds, product catalogs)
- Avoid the get() helper function in high-traffic collections — each rule evaluation triggers a Firestore read, which adds latency and billing cost
- Use Firebase Custom Claims via Admin SDK for role-based access in performance-sensitive apps — claims are in the JWT token, not a Firestore read
- Audit your rules quarterly — as your app evolves, old permissive rules on deprecated collections may remain unnoticed
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
Write Firestore security rules for a FlutterFlow app with these collections: (1) users/{userId} — only the owner can read/write their own profile, admins can read any profile, nobody can change the role field except via Cloud Functions, (2) posts/{postId} — any authenticated user can read, owner can create/update/delete, moderators can delete, (3) orders/{orderId} — owner can read their orders, nobody can delete or update orders. Use a helper function to fetch the user's role from their Firestore profile document.
My FlutterFlow app users are getting permission-denied errors when trying to read their own orders. The orders collection has a userId field containing the owner's Firebase UID. Write the Firestore security rule for the orders collection that allows users to read documents where the userId field matches their own UID, prevents users from reading other users' orders, and allows creating orders where the userId field matches the creating user's UID.
Frequently asked questions
What happens if I deploy wrong security rules and lock everyone out?
If your rules are too restrictive and block legitimate users, you can fix them immediately by editing the rules in Firebase Console → Firestore Database → Rules and clicking Publish. Rule changes take effect within 1 minute globally. Keep your Firebase Console access credentials secure since console access bypasses all Firestore rules — the Firebase Console uses admin-level access.
Can users bypass Firestore security rules from a FlutterFlow app?
No. Security rules are evaluated server-side on Google's infrastructure, not in the app. A user cannot modify the rules, skip rule evaluation, or bypass them by decompiling the app. The only bypass is the Firebase Admin SDK (used in Cloud Functions with a service account), which has full database access regardless of rules.
How do I allow public read access for some data (like a product catalog) while keeping other data private?
Write separate rules for each collection. For public data: allow read: if true (no auth check). For private data: allow read: if request.auth != null && request.auth.uid == resource.data.userId. You can mix public and private access within the same rules file by matching different collection paths.
Do Firestore security rules protect against Cloud Function access?
No. Cloud Functions using the Firebase Admin SDK (admin.firestore()) bypass all security rules. This is intentional — server-side code is trusted. Security rules only apply to client SDK calls (from FlutterFlow apps, web browsers, and mobile apps). This is why all sensitive operations (admin actions, payment processing) should happen in Cloud Functions, not client-side FlutterFlow actions.
How do I debug why my Firestore security rules are denying access?
Use the Firebase Console Rules Playground (Firestore → Rules → Rules Playground). Configure the operation, document path, and authentication context matching your failing scenario. The playground shows exactly which rule matched and why the request was allowed or denied. Also check the Firestore logs in Firebase Console → Logs Viewer filtered for 'permission_denied' events.
What if I need help designing security rules for a complex FlutterFlow app?
Security rule design for apps with multiple user roles, shared documents, and complex ownership hierarchies requires careful planning. RapidDev has secured FlutterFlow apps across healthcare, finance, and enterprise use cases and can design and audit your Firestore security architecture.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation