# How to Create a Custom Database in FlutterFlow

- Tool: FlutterFlow
- Difficulty: Intermediate
- Time required: 30-45 min
- Compatibility: FlutterFlow Free+ (Firestore), FlutterFlow Free+ (Supabase)
- Last updated: March 2026

## TL;DR

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.

## Before you start

- 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

### 1. 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.

### 2. 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.

### 3. 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.

```
// Example Backend Query configuration that requires a composite index:
// Collection: orders
// Filter: status == 'pending'  AND  userId == currentUserRef
// OrderBy: createdAt (Descending)
// Limit: 20
//
// Required composite index fields:
// Field 1: status   Direction: Ascending
// Field 2: userId   Direction: Ascending
// Field 3: createdAt Direction: Descending
//
// Create in Firebase Console:
// Firestore → Indexes → Composite → Add index
// Collection ID: orders
// Add the three fields above with their directions
// 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.

### 4. 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.

### 5. 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.

### 6. 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.

```
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    // Users can only read/write their own user document
    match /users/{userId} {
      allow read, write: if request.auth != null
                         && request.auth.uid == userId;
    }

    // Products: anyone authenticated can read, only admins write
    match /products/{productId} {
      allow read: if request.auth != null;
      allow write: if request.auth != null
                   && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin';
    }

    // Orders: users can read/write only their own orders
    match /orders/{orderId} {
      allow read, write: if request.auth != null
                         && request.auth.uid == resource.data.userId;
      allow create: if request.auth != null
                    && request.auth.uid == request.resource.data.userId;
    }

    // Cart items subcollection: user owns their own cart
    match /users/{userId}/cart_items/{itemId} {
      allow read, write: if request.auth != null
                         && request.auth.uid == userId;
    }
  }
}
```

**Expected result:** Security rules are deployed, allowing authenticated users to access only their own data while blocking unauthorized access.

## Complete code example

File: `Custom Database Schema Architecture`

```text
Firestore Schema Design
========================

Collection: users
├── Document ID: {Firebase Auth UID}
├── displayName: String
├── email: String
├── role: String          // 'user' | 'admin'
├── subscriptionTier: String  // 'free' | 'pro'
├── createdAt: Timestamp
├── avatarUrl: String
└── Subcollection: cart_items
    ├── Document ID: auto
    ├── productRef: DocumentReference → products
    ├── quantity: Integer
    └── addedAt: Timestamp

Collection: products
├── Document ID: auto
├── title: String
├── description: String
├── price: Double
├── imageUrl: String
├── stockCount: Integer
├── isActive: Boolean
├── categoryId: String
└── createdAt: Timestamp

Collection: orders
├── Document ID: auto
├── userId: DocumentReference → users
├── status: String        // 'pending' | 'paid' | 'shipped' | 'delivered'
├── total: Double
├── createdAt: Timestamp
└── Subcollection: order_items
    ├── productRef: DocumentReference → products
    ├── quantity: Integer
    └── priceAtPurchase: Double

Required Composite Indexes
===========================
orders: [userId ASC, createdAt DESC]
orders: [status ASC, createdAt DESC]
products: [categoryId ASC, isActive ASC, createdAt DESC]

Supabase Alternative (for relational needs)
============================================
users          id (uuid PK), email, display_name, role, created_at
products       id (uuid PK), title, price, stock_count, category_id, is_active
orders         id (uuid PK), user_id (FK→users), status, total, created_at
order_items    id (uuid PK), order_id (FK→orders), product_id (FK→products), qty, price

RLS Policy examples (Supabase SQL):
CREATE POLICY "users_own_data" ON orders
  FOR ALL USING (auth.uid() = user_id);

CREATE POLICY "public_products" ON products
  FOR SELECT USING (is_active = true);
```

## Common mistakes

- **Designing a deeply relational normalized schema in Firestore with many small collections that require multiple reads to reconstruct a single view** — Firestore has no JOIN operation. If displaying an order requires reading the order document, then the user document (via DocumentReference), then each product document (via order_items DocumentReference), you are doing N+2 reads for a single page load. At $0.06 per 100K reads, this adds up fast with real users. Fix: 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.
- **Creating all fields as String type because it is the most familiar type** — Storing prices as String ('29.99') means you cannot filter orders WHERE total > 50 or sort by price — Firestore string comparison puts '9' after '89' alphabetically. Storing timestamps as String means you cannot query events after a specific date. Fix: 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.
- **Skipping Firestore Security Rules and leaving the database in test mode (all reads/writes allowed) when building the MVP** — Firebase test mode rules expire after 30 days and make your entire database publicly readable and writable — any person on the internet can read all user data, delete all documents, or overwrite all products. Several FlutterFlow apps have been compromised this way. Fix: 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

## 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.

---

Source: https://www.rapidevelopers.com/flutterflow-tutorials/how-can-i-create-a-custom-database-in-flutterflow
© RapidDev — https://www.rapidevelopers.com/flutterflow-tutorials/how-can-i-create-a-custom-database-in-flutterflow
