Most migration guides sell the wrong part as the problem. Moving auth and storage from Firebase to Supabase is routine — predictable, documented, done in a sprint. What blows up projects is the data model.
By the time you read an article like this, the strategic call is usually already made: away from Google Cloud, towards Postgres and a self-hostable stack. The reasons behind it — data sovereignty, the cost curve, vendor lock-in — I've covered at length elsewhere. This is about the mechanics. What actually happens when you migrate Firebase to Supabase, in what order the pain arrives, and where the budget burns if you start unprepared.
Migrating Firebase to Supabase means fighting the data model
Firestore and Postgres hold fundamentally different worldviews. Firestore is a document-oriented NoSQL database optimised for read performance by duplicating data. You don't model by entities and relationships, you model by access patterns: what does this screen show? Exactly that lands in a document — redundant, denormalised, often several times over. An order carries a copy of the delivery address, the username sits in five collections because a join would be expensive.
Postgres flips that. A relational database lives on normalisation: one truth per fact, linked through foreign keys. The step from Firestore to PostgreSQL is therefore not a data transfer but a modelling task. You aren't translating rows, you're designing a schema from scratch. If you're not yet familiar with how Supabase is built, the fundamentals are in my overview of what Supabase is, technically.
The official migration guide makes that honestly visible. It describes the tool as something that "copies the entire contents of a single Firestore collection into a single Postgres table" (Supabase documentation on Firestore data migration). Three Node scripts handle the export, the JSON conversion and the import. Nested fields land in a jsonb column by default. It works — but it produces Postgres tables that still look like Firestore on the inside. One collection, one table, the rest as a JSON blob.
That's not a relational model. It's Firestore in a Postgres costume. Leave it there and you give away exactly the strengths you migrated for: joins, constraints, integrity, real queries. The clean reshaping — breaking collections apart, making relationships explicit, keeping jsonb where it earns its place and only there — is manual work and head work. And it's the line item where the hours pile up.
Firebase Auth migration: less dramatic than you'd think
Here's the good news that gets lost in most discussions: the Firebase Auth migration is not a break for your users. Nobody has to reset a password if you do it right.
The reason lies in how Supabase Auth — GoTrue internally — is built. GoTrue verifies Firebase scrypt hashes natively through a dispatcher that reads the $fbscrypt$ prefix to recognise which hashing method a stored password uses, alongside bcrypt and Argon2. In practice that means you export the users from Firebase, import them hash and all, and sign-in keeps working with the existing password. On the next successful login Supabase can quietly re-hash to bcrypt, its default for new passwords.
The procedure is mechanical. Two scripts from the community tooling firebase-to-supabase do the work: one exports the users as JSON, one imports them. The catch: you have to copy four SCRYPT parameters out of the Firebase console — base64_signer_key, base64_salt_separator, rounds and mem_cost. Without those four values GoTrue can't verify the hashes.
This is exactly where the only relevant pitfall of the auth migration sits. There's a documented bug where wrongly imported hashes land in the database with an empty encrypted_password field. The result: every login fails even though the users are seemingly present. The fix is the correct $fbscrypt$ import path. Use the official path, check with a test user beforehand, and you never hit the problem. Auth is predictable when you plan it — not because it's trivial, but because the path is fully documented and the behaviour deterministic. If you'd rather move off password login onto your own identity provider, it's worth evaluating Keycloak as an auth layer in parallel.
A note on the tooling: firebase-to-supabase is a community repo, not Supabase core. It covers auth, Firestore, storage and functions, but it's community-maintained, not a managed product. For a production move that means: read the scripts first, dry-run them in a staging environment, don't run them blind against the live database.
Firebase Storage migration and the thing about private buckets
The Firebase Storage migration follows a simple two-phase pattern: download, upload. Export files from Firebase Storage, write them into Supabase buckets. With large volumes you work with pagination and batches, otherwise you run into memory or timeout limits.
The one point that catches teams out: new Supabase buckets are private by default. In Firebase, access is often governed by a Security Rule, and much of it is effectively public-readable. Move files into Supabase and forget to set the access rules explicitly, and suddenly every avatar, thumbnail and PDF is unreachable — or, on the inverse mistake, public where it shouldn't be. That's not a technical problem, it's a checklist question. You decide per bucket, deliberately: private with signed URLs, or public. That's precisely the security gain, if you use it.
Storage, like auth, is a solved problem. Effort: manageable and linear with the data volume. Risk: low, as long as you know about the default privacy.
Migration pitfalls: Security Rules and Realtime
The real migration pitfalls hide in two places — and both hang off the concept, not the transfer.
Firestore Security Rules are path-based. You write rules along document paths, in a dedicated DSL that grants or denies access depending on request.auth and document content. In Supabase this translates into row level security: SQL policies per table, split by SELECT, INSERT, UPDATE and DELETE. This is not find-and-replace. The path-based logic has to be rethought as a relational condition — "may user X see this document" becomes "which rows of this table does the policy release for auth.uid()".
The payoff is considerable once you grasp it. RLS doesn't just filter direct queries, it automatically filters realtime subscriptions too. A correctly written policy thereby protects read access across every channel — an architectural advantage that Firestore rules don't offer in this form. Why row level security is more than a security feature, and how to structure policies cleanly, I've written about separately. For the migration, the rule is: budget time to rewrite and test the entire rule logic. This is not a side task.
The second conceptual break is realtime. Firestore's onSnapshot is more than a live update: it brings a local offline cache and conflict resolution, the app keeps working without a network and syncs later. Supabase Realtime streams Postgres WAL changes over WebSockets (documentation on Postgres Changes) — fast and clean, but without native offline persistence. If you relied on Firestore's offline behaviour, you build the client caching yourself, for example with TanStack Query. For a classic web app that's often a non-issue. For an offline-first mobile app it's a separate implementation block that visibly raises the cost of the migration.
How deep is the app in the Firebase ecosystem?
Here I have to name the counter-position fairly, because in many cases it's right. If an app uses only Firestore, Firebase Auth and storage, the move is a clearly bounded project. But if it sits deep in the Firebase mobile ecosystem, the maths changes fundamentally.
FCM for push notifications, Crashlytics for crash reporting, Remote Config for feature flags, ML Kit for on-device models, Firebase Analytics — none of these has a direct Supabase equivalent. Supabase has no push service of its own; push still runs over FCM or APNs, triggered from Edge Functions. So the migration doesn't remove the FCM dependency, it just shifts where you call it from. Crashlytics, Remote Config and ML Kit have no counterpart — you replace them through third parties like Sentry, or you simply keep Firebase for those parts.
That's the honest core of the trade-off: the more an app leans on these mobile-specific building blocks, the further the migration drifts from a pure database swap. "Firestore to PostgreSQL" then becomes "Firestore to PostgreSQL plus re-wiring FCM plus replacing Crashlytics plus rebuilding Remote Config". You have to price these items individually before you name a budget. A partial migration — database to Supabase, mobile telemetry staying on Firebase — is in such cases often the economically clean solution, not a compromise born of weakness.
What a migration realistically costs
Numbers without context are worthless, so here's the frame first. A clean initial implementation of a self-hosted Supabase stack — setup, integration, security, testing — runs, in our experience, to a one-off €5,000 to €20,000 in development effort. A migration comes on top and spreads itself highly unevenly.
Auth and storage are the cheap blocks: documented, deterministic, done in days. The data transfer itself — running the scripts, copying collections — is manageable too. What drives the bill is the part nobody likes to talk about: reshaping the data model, rewriting the Security Rules as RLS and, where relevant, rebuilding realtime offline behaviour and Firebase mobile features. At a blended rate of roughly €120 per developer hour, the complexity of the data model decides whether a migration lands at the lower or upper end of the range.
The strategic point behind it: the running costs of a self-hosted stack fall over time, while the cost of managed services rises with the project's success. A migration is a one-off investment against a cost curve that reverses. How that plays out over three years and what realistic operating costs look like, I cover in the TCO comparison for Supabase in production. If you're after the full strategic logic behind the switch — why Supabase is a serious sovereignty option for DACH organisations — you'll find it on our Supabase overview page.
Some apps should not migrate
Now the uncomfortable part, which I'll state openly even though happycoding earns money on migrations. Not every Firebase app belongs on Supabase. Some should stay exactly where they are.
If an app lives mostly on Firebase-specific mobile features — push, Crashlytics, Remote Config, ML Kit, on-device magic — and there's no genuine sovereignty or cost requirement behind it, then a migration is an expensive move without a destination. You swap a working architecture for another, rebuild half the Firebase ecosystem through detours, and pay development budget that produces no measurable advantage anywhere. You'd be migrating for a feeling, not for a requirement.
The honest test comes down to two questions. First: is there a concrete driver — GDPR pressure, a data-sovereignty mandate from the board, a Firestore cost curve that explodes with success? The data-protection dimension I've worked through separately, because Google too, as a US corporation, stays subject to the CLOUD Act despite an EU storage location — read it in my piece on data protection as a strategic decision. Second: does the app move mostly on the data layer, or does its value hang on mobile-specific Firebase infrastructure? If the value sits in the data and there's a driver, the migration almost always pays off. If the value hangs on Firebase-only features and the driver is missing, staying put is the professional answer.
What I tell decision-makers
Treat the migration not as a data transfer but as a re-architecture of your data model — because that's exactly what it is. Auth, storage and the raw export are solved problems; plan the data model as the main risk and you plan past the risk. The money and the time flow into reshaping Firestore collections into normalised Postgres tables, into translating the Security Rules into row level security, and into everything that hangs off the Firebase mobile ecosystem.
Before any script runs, an honest inventory belongs on the table: how deep does the app sit in the ecosystem, how nested is the data model, is there a strategic driver beyond "Postgres sounds better". Come out of that check cleanly and the migration is one of the soundest investments in technological sovereignty you can make. Come out of it murky, and the braver decision is not to migrate — and to spend the budget where it makes a real difference.
