If you’re planning to migrate a Flutter app from Firebase to Supabase, the short version is this: you’re not swapping one backend for a similar one, you’re switching data paradigms entirely — from Firestore’s document-and-collection model to a relational Postgres database, from Firebase’s proprietary security rules to Postgres Row Level Security, and from four separate Firebase SDKs to a single supabase_flutter package. Done carelessly, that’s a rewrite. Done deliberately, in the order this guide lays out, it’s a well-scoped engineering project most teams complete in a few weeks without users ever noticing a cutover happened.
This is the exact process — schema design, authentication, data, storage, real-time listeners, Cloud Functions, and a zero-downtime rollout — that a real migration follows end to end. Where the underlying work is a data-architecture and cloud-migration problem rather than a Flutter UI problem, it’s also the part of this project our database migration engineers get pulled into most often, since getting the Postgres schema and security model right the first time is what determines whether the rest of the migration goes smoothly.
- Why Flutter Teams Are Migrating from Firebase to Supabase
- Firebase vs. Supabase: What Actually Changes for a Flutter App
- Before You Migrate: Audit Your Firebase Project
- The Migration Process, Step by Step
- Updating Your Flutter Code and Dependencies
- Common Migration Mistakes to Avoid
- How Long Does It Take, and What Does It Cost?
Why Flutter Teams Are Migrating from Firebase to Supabase
Firestore’s free Spark tier covers 50,000 reads, 20,000 writes, and 20,000 deletes a day, and the pay-as-you-go Blaze plan bills at roughly $0.06 per 100,000 reads and $0.18 per 100,000 writes — cheap in isolation, but with no hard spending cap, a single unbounded query or a looping Cloud Function can turn a predictable bill into a five-figure surprise overnight. That’s the trigger most teams describe first, but it’s rarely the only reason a migration gets greenlit. The others tend to be architectural: Firestore has no server-side JOINs, so any query spanning more than one collection means either denormalizing data (and keeping every copy in sync by hand) or fetching documents client-side and stitching them together in Dart — a pattern that gets painful fast once an app has more than a handful of related entities.
Postgres, by contrast, is a relational database that Flutter teams already know how to model — and it isn’t a niche choice. PostgreSQL is now the most-used database among professional developers worldwide, according to the 2025 Stack Overflow Developer Survey, and Supabase’s growth reflects the same shift: the company raised a $500M round at a $10.5B valuation in mid-2026, with over 9 million registered developers building on its Postgres-based platform. For a Flutter team, the practical upside is a single Postgres database with real foreign keys, SQL views, and transactions, plus Supabase’s own Auth, Storage, Realtime, and Edge Functions layered directly on top of it — replacing Firebase’s four separate products with one coherent stack.
The most common triggers for this migration, in order: unpredictable Blaze billing at scale, the need for relational queries Firestore can’t express natively, wanting an open-source stack without vendor lock-in, and — increasingly — teams that started on Firebase for speed during an MVP and have now outgrown its data model as the app’s schema got more interconnected.
Firebase vs. Supabase: What Actually Changes for a Flutter App
Every Firebase product a typical Flutter app depends on has a Supabase equivalent — except one. Mapping them out before you touch any code is what turns “migrate the backend” into a concrete, step-by-step checklist:
| Firebase Product | Supabase Equivalent | What Changes |
|---|---|---|
| Firestore | Postgres database | Document/collection model → relational tables with foreign keys; requires real schema design |
| Firebase Authentication | Supabase Auth (GoTrue) | Users move to the auth.users table; social/OAuth providers reconfigured; passwords need a migration strategy |
| Firebase Storage | Supabase Storage | Files re-uploaded to Supabase buckets; access rules rewritten as Storage policies |
| Cloud Firestore security rules | Row Level Security (RLS) | Rules become SQL policies enforced directly by Postgres, not a separate rules engine |
| Cloud Functions | Edge Functions (Deno) | Node.js functions rewritten in TypeScript/Deno; triggers reconfigured via Database Webhooks |
| Firestore real-time listeners | Supabase Realtime | .snapshots() streams become Postgres change subscriptions on specific tables |
| Firebase Cloud Messaging (FCM) | No native equivalent | Most teams keep Firebase (free tier) solely for push notifications alongside Supabase for everything else |

Before You Migrate: Audit Your Firebase Project
A migration that starts with an honest inventory of the current project goes faster than one that starts with code. Before writing a single line of Postgres schema, confirm:
- Every Firestore collection and its document shape — including collections used by only one screen, which are easy to forget until users hit a broken feature post-launch.
- Every Firebase Auth provider in use — email/password, Google, Apple, phone auth — since each has a different migration path and some (phone auth in particular) require extra planning.
- Current Firestore security rules, translated mentally into “who can read/write what” — this becomes your Row Level Security policy spec.
- Every Cloud Function, what triggers it (HTTP call, Firestore write, scheduled job), and what it actually does.
- Storage bucket structure and access patterns — public files vs. user-scoped private files need different Supabase Storage policies.
- Anything depending on Firebase Cloud Messaging, since that’s the one piece with no direct Supabase replacement.
The Migration Process, Step by Step
With the audit done, the migration itself follows a consistent sequence. Each step builds on the previous one, and skipping the order — schema before data, data before code changes — is the single most common cause of a migration that drags on far longer than planned.
Step 1: Design your Postgres schema from your Firestore collections
This is the step that determines how smoothly everything after it goes. For each Firestore collection, decide whether it becomes a single Postgres table or splits into several normalized tables — a Firestore document with a nested array of items (like an order with line items) typically becomes a parent table plus a related child table joined by a foreign key, rather than one table with a JSON column holding the array. Gart’s own case study on moving a production e-commerce workload from MongoDB to a relational database covers this exact document-to-relational modeling exercise; the shift from Firestore to Postgres follows the same logic, since both are document-oriented NoSQL stores being replaced by a relational schema. Our broader comparison of document versus relational databases is a useful reference if your team is still deciding how far to normalize.
Step 2: Set up your Supabase project and Row Level Security
Create the Supabase project, apply your schema via SQL migrations (kept in version control from day one — this is not optional for a production app), and translate every Firestore security rule into a Postgres RLS policy. RLS runs inside Postgres itself rather than as a separate rules layer, which means policies are testable with plain SQL and enforced no matter which client — Flutter app, Edge Function, or an admin script — touches the table.
-- Example: users can only read their own rows
create policy "Users can view own profile"
on profiles for select
using ( auth.uid() = user_id );
Step 3: Migrate Firebase Authentication users
Supabase publishes an official set of open-source migration tools for exactly this handoff: firestoreusers2json exports every Firebase Auth user to a JSON file, and import_users loads that file directly into Supabase’s auth.users table. Existing password hashes migrate too, provided you export Firebase’s scrypt hash parameters (signer key, salt separator, rounds) from the console first — done correctly, users log in with their existing password on Supabase without a forced reset. Social/OAuth providers (Google, Apple) need to be reconfigured as separate Supabase Auth providers, since the underlying provider credentials don’t transfer automatically.
Step 4: Migrate Firestore data to Postgres
The same open-source toolchain handles data: firestore2json dumps a Firestore collection to a flattened JSON file, and json2supabase loads it into the Postgres table you designed in Step 1. For collections that need to split into multiple related tables rather than one flat table, the tooling supports custom “hooks” — small scripts that reshape a document into several output records before import. Run this against a staging Supabase project first and diff row counts against Firestore before touching production data.
Step 5: Migrate Firebase Storage files
File migration is a two-step download-then-upload process: files come out of the Firebase Storage bucket to a local filesystem, then go up into a Supabase Storage bucket. Set bucket-level access policies (public vs. authenticated-only) before the first user hits the app post-cutover — new Supabase Storage buckets default to private, unlike some Firebase Storage configurations teams may have loosened over time.
Step 6: Replace Cloud Functions with Edge Functions
Cloud Functions triggered by HTTP calls port over conceptually unchanged — an Edge Function is still a serverless function behind a URL, just written in TypeScript on Deno instead of Node.js. Functions triggered by a Firestore write need more thought, since Supabase’s equivalent is a Database Webhook that fires on a Postgres insert/update/delete and calls an Edge Function — the trigger model is push-based via webhook rather than an SDK-level `onCreate`/`onUpdate` listener, so this is usually the part of the migration that takes the most debugging time.
Step 7: Decide what happens to push notifications
This is the one gap in the comparison table above: Supabase has no built-in equivalent to Firebase Cloud Messaging. The typical pattern is a hybrid setup — Supabase for the database, auth, storage, and business logic, with Firebase kept solely for FCM, called from a Supabase Edge Function when a relevant database row changes. It’s not an elegant answer, but it’s the one nearly every team ends up with, and it’s worth deciding on explicitly rather than discovering it mid-migration.
Step 8: Dual-run, test, and cut over with zero downtime
Ship a version of the app that can read from Supabase while Firebase still holds the source of truth, verify parity on real user accounts in a staged rollout, then flip writes over and decommission Firebase last — not first. A CI/CD pipeline that can deploy the Supabase-pointing build to a percentage of users, combined with feature flags around the data layer, turns the cutover into a controlled rollout instead of a single risky release. This is also the point where SRE practices — monitoring query latency, error rates, and auth success rates on both backends side by side — catch schema or RLS mistakes before they reach every user.
Updating Your Flutter Code and Dependencies
Firebase’s Flutter integration is spread across several packages; Supabase’s is not. Swapping pubspec.yaml dependencies is the visible part of the migration, even though it’s the smallest part of the actual work:
| Before (Firebase) | After (Supabase) |
|---|---|
firebase_core | supabase_flutter (single package) |
cloud_firestore | |
firebase_auth | |
firebase_storage |
Query syntax changes from Firestore’s document-and-collection API to Postgres-style filtering, and real-time listeners move from .snapshots() to Supabase’s stream API:
// Firestore: real-time query
FirebaseFirestore.instance
.collection('posts')
.where('userId', isEqualTo: uid)
.snapshots();
// Supabase: equivalent real-time query
supabase
.from('posts')
.stream(primaryKey: ['id'])
.eq('user_id', uid);
The shape of the code stays recognizable — a filtered, real-time stream of rows either way — which is why most teams find the client-side rewrite faster than the backend migration itself once the schema and RLS policies are in place.
Common Migration Mistakes to Avoid
- Copying the Firestore data model into Postgres as JSONB columns. It “works” on day one and defeats the entire point of moving to a relational database — you lose joins, foreign key integrity, and most of the query flexibility that motivated the migration.
- Writing RLS policies after launch instead of before. A table with no RLS policy and RLS enabled blocks all access by default; a table with RLS disabled is wide open. Both are easy to get backwards under deadline pressure — test every policy against a non-owner user before cutover, not after.
- Forgetting phone-auth and MFA users during the Auth migration. Email/password and OAuth users move cleanly with the standard tooling; phone-verified and multi-factor accounts often need a custom migration path that’s easy to miss during planning.
- Decommissioning Firebase before the dual-run period proves out. Keep both backends live and Firebase billing active until Supabase has handled real production traffic for at least one full billing cycle.
- Underestimating the Cloud Functions rewrite. HTTP-triggered functions port over almost directly; Firestore-triggered functions rebuilt around Database Webhooks are a different execution model and consistently take longer than teams budget for.
How Long Does It Take, and What Does It Cost?
For a small-to-mid-size Flutter app (a handful of Firestore collections, standard email/OAuth auth, a few Cloud Functions), a careful migration following the steps above commonly runs two to six weeks for a small team working part-time on it, or one to two weeks of focused effort for a dedicated pair of engineers. Apps with heavily denormalized Firestore data, custom claims-based security rules, or many Firestore-triggered Cloud Functions push toward the longer end of that range, since schema redesign and Edge Function rewrites — not the mechanical data export — are what actually consume the time. On the cost side, the calculation that usually justifies the project isn’t the migration effort itself; it’s comparing that one-time cost against an uncapped Blaze bill that’s already trending upward month over month, which is the same comparison our cloud migration engagements run for any workload moving off a metered, per-operation billing model.
Planning a Firebase-to-Supabase migration for a production Flutter app?
Gart Solutions handles the database and cloud engineering side of backend migrations end to end — Postgres schema design from your existing NoSQL data, Row Level Security policy modeling, and a zero-downtime cutover plan — so your Flutter team can focus on the app instead of the migration mechanics.


