Migrating from Firebase to Supabase: A Practical Playbook
How to move a production app off Firebase onto Supabase without downtime. The data model shift, the auth migration, the cutover plan — and the parts that always take longer than you expect.
Most Firebase → Supabase migrations we run start with the same realization: Firestore was great for shipping fast, but the data model has become a liability. Document fan-out, security rules sprawl, and query limitations end up costing more than the operational simplicity ever saved.
This is the playbook we use to move teams over without downtime.
Phase 1: Audit (1–2 weeks)
Before you move a single row, you need to know what you're moving.
- Schema discovery. Firestore doesn't have a schema, but your app does — somewhere in your TypeScript types, your security rules, or your team's heads. Reconstruct it. Identify every collection, every nested map, every array-of-objects that's about to become a join table.
- Access pattern audit. List every read and write. Which collections are queried by user? By relation? By time range? This drives the Postgres schema design more than any abstract modeling exercise will.
- Security rules → RLS mapping. Every Firestore security rule becomes one or more RLS policies. Most are simple. A few will reveal that your security model was actually held together by client-side checks. Find those now.
- Auth audit. How are users authenticated today? Email/password? Google? Anonymous? Custom claims? Each one has a different migration path in Supabase Auth.
You finish this phase with a written schema (the Postgres tables), a mapped policy list (Firestore rule → RLS policy), and a risk register (the surprises).
Phase 2: Build the target (1–2 weeks)
Stand up Supabase. Create the schema, write the RLS policies, write the helper functions. Add the indexes that the access patterns from Phase 1 told you about — don't skip this.
A few things we always do at this stage:
- Use
textfor natural IDs only. For surrogate keys, useuuidwithgen_random_uuid(). - For every table with RLS, write all four policies (select / insert / update / delete) — explicit is better than implicit.
- Wrap auth checks in
security definer stablefunctions so RLS doesn't kill performance. See Supabase RLS Gotchas for the patterns we watch for. - Write at least one pgTAP test or app-level RLS test per table before moving on.
Phase 3: Data migration script (1 week)
Write a script that reads from Firestore and writes to Supabase. Make it idempotent — you will run it more than once.
Two things to think hard about:
- Type translation. Firestore
Timestamp→ Postgrestimestamptz. Firestore document refs → foreign keys. Nested maps → eitherjsonbcolumns or normalized child tables (usually you want normalized). - ID stability. Firestore document IDs are strings. If you want to preserve them, store the Firestore ID as a column and put a unique index on it. New IDs are Postgres
uuid. Don't try to use Firestore IDs as primary keys long-term — they're not stable across collection scopes.
Run the script against a staging Supabase project. Compare row counts, spot-check rows, compare a few queries side-by-side.
Phase 4: Auth migration
This is the part most teams under-estimate. You can't just copy Firebase Auth users into Supabase Auth — password hashes use different algorithms, and Supabase won't accept Firebase's scrypt variant directly.
The pattern we use:
- Import the Firebase user list into Supabase with a
firebase_uidclaim and a placeholder password hash that nobody can ever match. - Build a sign-in shim: on first sign-in attempt after migration, the app calls a Supabase Edge Function. The function checks Firebase Auth with the submitted credentials. If they're valid, it sets the real Supabase password (Supabase hashes it), then signs the user in.
- After 30–60 days, decommission the shim. Force-reset anyone who hasn't logged in.
This is invisible to users and lets you retire Firebase Auth on your own timeline.
Phase 5: Cutover (1 day, planned)
The lowest-drama cutover we've shipped:
- Run the data migration script one more time against production data, in a freeze window.
- Flip a feature flag in the app: writes go to Supabase, reads go to Supabase.
- Keep Firebase writable for 24 hours as a rollback option. Don't read from it.
- After 24 hours of clean production traffic, revoke Firebase writes.
Zero downtime is achievable if your auth shim is working and your data migration script is idempotent.
Phase 6: Tune (1–2 weeks, ongoing)
Production traffic will reveal queries you missed in audit. Expect to add 3–5 indexes in the first week. Expect to refactor 1–2 RLS policies once you see actual query plans.
This is normal. Schedule the time.
Total timeline
For a typical Series A / B production app: 4–8 weeks end-to-end. The variables are auth complexity and how many integrations depend on Firestore directly (analytics pipelines, BigQuery exports, etc.).
If you're staring down a Firebase → Supabase migration and want a fixed-scope audit before committing to a date, get in touch. We've shipped this playbook on production apps and the audit alone usually saves more time than it costs.
Related reading:
- Supabase RLS Gotchas: 7 Patterns That Bite in Production — the RLS patterns you'll need in your new schema.
- How Much Does a Supabase Consultant Cost in 2026? — what a migration engagement actually costs.