Build a custom database in FlutterFlow by defining Firestore collections and fields directly in the Firestore panel, or connect Supabase for relational data. Design your schema with proper field types, use DocumentReference for relationships, create subcollections for nested data, and add composite indexes for compound queries. Use FlutterFlow's Content Manager for GUI data entry during development.
Design your database schema inside FlutterFlow before writing a single query
Most FlutterFlow developers jump straight to building UI before thinking about their data model, then hit walls when queries return unexpected results or relationships break. This tutorial walks through designing a complete database schema from scratch — defining collections, choosing field types, modeling relationships with DocumentReference and subcollections, and setting up composite indexes. You will also learn when Supabase's relational PostgreSQL is a better fit than Firestore's document model, and how to use FlutterFlow's Content Manager to populate test data without writing Cloud Functions.
Prerequisites
- A FlutterFlow project with Firebase connected (Settings → Firebase → Connect Project)
- A Firebase project with Firestore enabled in the Firebase Console (Build → Firestore Database → Create database)
- Basic understanding of what a database collection and document are
- Your app's data requirements sketched out (what entities you need: users, products, orders, etc.)
Step-by-step guide
Open the Firestore panel and create your first collection
Open the Firestore panel and create your first collection
In FlutterFlow's left navigation, click the Firestore icon (cylinder icon) to open the Firestore panel. Click the + button to add a new collection. Name it using plural lowercase with underscores — for example: users, products, orders. For each collection, FlutterFlow shows a field editor. Every Firestore document automatically has a document ID — you do not need to add an id field manually. Add your first field by clicking + Add Field. Choose the field name (e.g., title) and type. FlutterFlow's field types map directly to Firestore: String (text), Integer (whole numbers), Double (decimals), Boolean (true/false), Timestamp (dates and times), DocumentReference (pointer to another document), LatLng (coordinates), Blob (binary data), JSON (unstructured data), Array (list of same-type values), and Map (nested key-value pairs). Create your core collections — a typical app needs users (displayName: String, email: String, createdAt: Timestamp, subscriptionTier: String), products (title: String, description: String, price: Double, imageUrl: String, stockCount: Integer, isActive: Boolean), and orders (userId: DocumentReference → users collection, total: Double, status: String, createdAt: Timestamp).
Expected result: Your core collections appear in the Firestore panel with all fields defined and typed correctly.
Model relationships with DocumentReference and subcollections
Model relationships with DocumentReference and subcollections
Firestore is a document database — there are no JOIN operations. Relationships are modeled in two ways. First, DocumentReference: when one document needs to point to another, add a field of type DocumentReference and specify which collection it points to. For example, on the orders collection add a field userId of type DocumentReference → users. In FlutterFlow, when you set this field, you select a specific user document, and when you query it later you can use 'Reference' in your Backend Query to get the referenced document's fields. Second, subcollections: for one-to-many relationships where you frequently query the child items in isolation, use subcollections. Example: users/{uid}/cart_items is a subcollection under each user document. In FlutterFlow's Firestore panel, click a collection → Add Subcollection → name it cart_items → add fields (productRef: DocumentReference, quantity: Integer, addedAt: Timestamp). The path users/{uid}/cart_items means each user has their own cart isolated from other users' carts — efficient to query with a single-document path filter.
Expected result: Relationships are visible in the Firestore panel as DocumentReference fields and nested subcollections with their own field definitions.
Add composite indexes for compound queries
Add composite indexes for compound queries
Firestore automatically indexes every field for single-field queries. But if your Backend Query uses more than one field in a where clause, or combines a where clause with an orderBy on a different field, Firestore requires a composite index. Without it, the query returns an error with a direct link to create the needed index. In FlutterFlow: go to your Firestore panel → select a collection → click the Indexes tab → click Add Index. Define the fields and their sort directions. Example: to query orders where status == 'pending' ordered by createdAt descending, add a composite index on [status ASC, createdAt DESC]. You can also create indexes directly in the Firebase Console under Firestore → Indexes → Composite → Add index — use the exact error link from the Flutter console (visible in Run mode or exported code) to auto-populate the index fields. Indexes take 2-5 minutes to build. During development, keep a note of every compound query you add so you remember to create its index before deploying.
1// Example Backend Query configuration that requires a composite index:2// Collection: orders3// Filter: status == 'pending' AND userId == currentUserRef4// OrderBy: createdAt (Descending)5// Limit: 206//7// Required composite index fields:8// Field 1: status Direction: Ascending9// Field 2: userId Direction: Ascending10// Field 3: createdAt Direction: Descending11//12// Create in Firebase Console:13// Firestore → Indexes → Composite → Add index14// Collection ID: orders15// Add the three fields above with their directions16// Click Create index (builds in ~3 minutes)Expected result: Composite indexes are created for every compound query in your app so queries execute without errors.
Choose Supabase instead of Firestore for relational data
Choose Supabase instead of Firestore for relational data
If your data model requires true relational queries — JOINs between tables, foreign key constraints, aggregate functions (SUM, COUNT, AVG), or complex filtering across multiple related tables — Supabase's PostgreSQL is a better choice than Firestore. Connect Supabase in FlutterFlow: Settings → Integrations → Supabase → enter your Supabase Project URL and Anon Key (from Supabase Dashboard → Settings → API). Once connected, FlutterFlow's Backend Query panel shows a Supabase Query option. You write SQL-style queries in FlutterFlow's query builder: SELECT fields, FROM table, WHERE conditions, ORDER BY, LIMIT. Create tables in Supabase Dashboard → Table Editor → New table, or using the SQL Editor. Define foreign keys: orders.user_id → users.id for referential integrity. Important: enable Row Level Security (RLS) on every table immediately — without RLS, any authenticated user can read all data. In the SQL Editor run: ALTER TABLE orders ENABLE ROW LEVEL SECURITY; then create policies for read/write access.
Expected result: Supabase is connected in FlutterFlow and your relational tables are visible in the Backend Query panel with SQL query capability.
Enter test data using FlutterFlow's Content Manager
Enter test data using FlutterFlow's Content Manager
FlutterFlow's Content Manager lets you add, edit, and delete Firestore documents without writing code or visiting the Firebase Console. Click the Content Manager icon in the left navigation (grid icon). Select a collection from the dropdown. Click + Add Document — a form appears with all the fields you defined. Fill in values for each field: strings, numbers, timestamps (date picker), booleans (toggle). For DocumentReference fields, a dropdown shows existing documents in the referenced collection — select the correct one. For Array fields, add comma-separated values. For Map fields, enter key-value pairs. Repeat for 3-5 documents per collection so your UI has realistic data to display during development. This is significantly faster than manually entering data in the Firebase Console. Note: Content Manager writes directly to your project's live Firestore — use a dev Firebase project during development, not your production database.
Expected result: At least 3-5 test documents per collection appear in the Content Manager and are immediately queryable in FlutterFlow's Run mode.
Set Firestore Security Rules to protect your data
Set Firestore Security Rules to protect your data
By default, new Firestore databases are created in locked mode (all reads/writes denied). You must write security rules before your app can access data. In FlutterFlow: Firestore panel → Security Rules tab → you can write rules directly here, then click Deploy. Basic rules for an authenticated app: allow read, write: if request.auth != null (any authenticated user can access everything — too permissive for production). Better user-scoped rule: for the users collection, allow read, write: if request.auth.uid == resource.data.userId. For the orders subcollection: allow read, write: if request.auth.uid == get(/databases/$(database)/documents/users/$(userId)).data.uid. Test rules in Firebase Console → Firestore → Rules → Rules Playground before deploying. Clicking 'Deploy Rules' in FlutterFlow pushes rules to Firebase immediately — changes take effect within 60 seconds.
1rules_version = '2';2service cloud.firestore {3 match /databases/{database}/documents {45 // Users can only read/write their own user document6 match /users/{userId} {7 allow read, write: if request.auth != null8 && request.auth.uid == userId;9 }1011 // Products: anyone authenticated can read, only admins write12 match /products/{productId} {13 allow read: if request.auth != null;14 allow write: if request.auth != null15 && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin';16 }1718 // Orders: users can read/write only their own orders19 match /orders/{orderId} {20 allow read, write: if request.auth != null21 && request.auth.uid == resource.data.userId;22 allow create: if request.auth != null23 && request.auth.uid == request.resource.data.userId;24 }2526 // Cart items subcollection: user owns their own cart27 match /users/{userId}/cart_items/{itemId} {28 allow read, write: if request.auth != null29 && request.auth.uid == userId;30 }31 }32}Expected result: Security rules are deployed, allowing authenticated users to access only their own data while blocking unauthorized access.
Complete working example
1Firestore Schema Design2========================34Collection: users5├── Document ID: {Firebase Auth UID}6├── displayName: String7├── email: String8├── role: String // 'user' | 'admin'9├── subscriptionTier: String // 'free' | 'pro'10├── createdAt: Timestamp11├── avatarUrl: String12└── Subcollection: cart_items13 ├── Document ID: auto14 ├── productRef: DocumentReference → products15 ├── quantity: Integer16 └── addedAt: Timestamp1718Collection: products19├── Document ID: auto20├── title: String21├── description: String22├── price: Double23├── imageUrl: String24├── stockCount: Integer25├── isActive: Boolean26├── categoryId: String27└── createdAt: Timestamp2829Collection: orders30├── Document ID: auto31├── userId: DocumentReference → users32├── status: String // 'pending' | 'paid' | 'shipped' | 'delivered'33├── total: Double34├── createdAt: Timestamp35└── Subcollection: order_items36 ├── productRef: DocumentReference → products37 ├── quantity: Integer38 └── priceAtPurchase: Double3940Required Composite Indexes41===========================42orders: [userId ASC, createdAt DESC]43orders: [status ASC, createdAt DESC]44products: [categoryId ASC, isActive ASC, createdAt DESC]4546Supabase Alternative (for relational needs)47============================================48users id (uuid PK), email, display_name, role, created_at49products id (uuid PK), title, price, stock_count, category_id, is_active50orders id (uuid PK), user_id (FK→users), status, total, created_at51order_items id (uuid PK), order_id (FK→orders), product_id (FK→products), qty, price5253RLS Policy examples (Supabase SQL):54CREATE POLICY "users_own_data" ON orders55 FOR ALL USING (auth.uid() = user_id);5657CREATE POLICY "public_products" ON products58 FOR SELECT USING (is_active = true);Common mistakes when creating a Custom Database in FlutterFlow
Why it's a problem: Designing a deeply relational normalized schema in Firestore with many small collections that require multiple reads to reconstruct a single view
How to avoid: Denormalize: duplicate frequently-read data into each document. Store userName and userEmail directly on the order document instead of only a DocumentReference to users. Store productTitle and productImageUrl in order_items instead of only productRef. Accept that some data is duplicated — it is a deliberate trade-off for read performance in NoSQL.
Why it's a problem: Creating all fields as String type because it is the most familiar type
How to avoid: Use correct Firestore types: prices as Double, quantities as Integer, dates as Timestamp, true/false flags as Boolean. Only use String for actual text values. In FlutterFlow's field definition, be deliberate about selecting the right type from the dropdown.
Why it's a problem: Skipping Firestore Security Rules and leaving the database in test mode (all reads/writes allowed) when building the MVP
How to avoid: Write and deploy security rules before sharing the app URL with anyone. Start with: allow read, write: if request.auth != null to require authentication, then narrow down to per-user rules. Deploy via FlutterFlow's Firestore panel → Security Rules → Deploy.
Best practices
- Plan your data model on paper before opening FlutterFlow — list every entity (noun in your app), their fields, and how they relate to each other
- Use Timestamp fields for all date/time data and store in UTC — FlutterFlow's DateTimePicker returns local time so convert to UTC before writing
- Keep documents under 1MB — if a document's Map or Array fields grow unbounded (e.g., a messages array in a chat doc), move them to a subcollection
- Add a createdAt: Timestamp field to every collection — it is almost always needed for sorting and you cannot add it retroactively to existing documents without a migration script
- Use FlutterFlow's Content Manager to add test data during development rather than hardcoding dummy data into your UI widgets
- For Supabase: enable RLS immediately on every table you create — a table without RLS is fully public to anyone with your anon key
- Document your schema in a comment in your FlutterFlow project description or a Firestore README so collaborators understand the data model without reverse-engineering it
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I am building a FlutterFlow app and need to design a Firestore database schema. My app is [describe your app — e.g., 'a marketplace where sellers list products and buyers place orders']. Help me design: (1) what collections I need, (2) what fields each collection should have with proper Firestore types (String, Integer, Double, Boolean, Timestamp, DocumentReference, Array), (3) which relationships to model as DocumentReference vs subcollections, and (4) what composite indexes I will need for common queries like listing a user's orders sorted by date or filtering active products by category.
Create a Backend Query on the orders page that fetches all orders where userId equals the current user's document reference, ordered by createdAt descending, limited to 20 results. Bind the results to a ListView and display each order's status, total, and createdAt fields.
Frequently asked questions
Should I use Firestore or Supabase for my FlutterFlow app?
Choose Firestore if your data is document-centric (each entity is self-contained with few cross-entity queries), you need real-time listeners, or you are already using Firebase Auth and Storage. Choose Supabase if your data is genuinely relational (multiple tables with JOINs), you need aggregate queries (SUM, COUNT, GROUP BY), or you prefer SQL. Many apps use both: Firestore for real-time features like chat or notifications, Supabase for complex business data like reporting.
How do I query across multiple collections in Firestore?
Firestore does not support cross-collection JOINs. Your options are: (1) Denormalize — store needed fields from related documents directly in each document. (2) Multiple queries — run two Backend Queries and combine results in a Custom Function. (3) Collection Group Query — query across all subcollections with the same name (e.g., query all cart_items subcollections across all users). (4) Use Supabase if your query patterns require true JOIN operations.
How many collections can I create in Firestore?
There is no limit on the number of collections or documents. Firestore scales automatically. The limits that matter are: document size max 1MB, 1 write per second per document (use separate documents for high-frequency writes), and 200 composite indexes per database. Design your schema for your query patterns, not around artificial limits.
Can I rename a collection or field after I have created it?
No — Firestore has no rename operation. To rename a collection, you must create a new collection, write a Cloud Function to copy all documents, update all your FlutterFlow Backend Queries to use the new name, then delete the old collection. This is expensive in production. Plan your naming conventions carefully before launch. For field renaming, it is the same process per document — often easier to add the new field + deprecate the old one over time.
What is the difference between a subcollection and an Array field?
Array fields store a flat list of values inside a document — good for small, bounded lists like a list of tag strings or user interest categories. Subcollections are separate collections nested under a document — good for unbounded lists of complex objects, like a user's order history or a post's comments. Use subcollections when: the list could have thousands of items, each item has multiple fields, or you need to query the child items independently.
Can RapidDev help design the right database schema for my app?
Yes. Choosing between Firestore and Supabase, deciding what to denormalize, and planning for future query patterns requires experience with both databases at scale. RapidDev reviews your app requirements and designs a schema optimized for your specific read/write patterns — avoiding the expensive rewrites that come from discovering the wrong schema after launch.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation