A practical look at the reasons behind the move, what happens to your data and auth, and how to plan a migration that doesn’t break production.
Firebase is a great way to ship a first version fast. Firestore, Auth, and Storage come pre-wired, and for a prototype or an early MVP, that speed is exactly what you need. The problems tend to show up later — once the data model has real relationships, the bill has real traffic behind it, and the team wants row-level access control instead of a parallel rules language to maintain.
That’s the point where a Firebase to Supabase migration usually enters the conversation. This guide walks through why teams make the move, what actually changes under the hood — data model, authentication, storage, realtime — and how complex the process really is, based on the patterns we see across migrations at Gart Solutions.
Firestore’s NoSQL model struggles with relational data and complex queries; Postgres handles both natively, with real joins instead of denormalization workarounds.
Firebase billing scales with reads, writes, deletes, and bandwidth — which gets unpredictable fast. Supabase’s pricing is flatter and easier to plan against.
Supabase ships Row Level Security, instant REST and GraphQL APIs, and Realtime on top of standard Postgres — no separate rules DSL to maintain.
Migration complexity is moderate: reshaping the data model is the hard part, while Auth has an official, well-documented export/import path.
Gart Solutions plans and executes migrations of any complexity — from Firebase specifically, and from the wider Postgres and BaaS ecosystem more broadly.
Why teams are leaving Firebase for Supabase
Firestore wasn’t built for relational data
Firestore is a document database. That’s fine for flat, self-contained records — a user profile, a single chat message — but most real products don’t stay flat for long. Orders reference customers and inventory. Bookings reference venues, staff, and availability windows. The moment your data has real relationships, Firestore forces a choice: denormalize (copy data into multiple documents and keep it in sync by hand) or fan out writes across collections to simulate a join that a relational database gives you for free.
Composite indexes are often the tell. If your Firestore console is full of them, it’s usually a sign the underlying data wants to be relational and is being modeled around a document store instead of with one. Postgres — the database underneath Supabase — handles joins, foreign keys, and multi-table queries as first-class operations, because that’s what it was designed for.
Firebase bills are hard to predict
Firebase’s pricing model charges per document read, write, and delete, plus bandwidth and storage. That’s manageable at low volume, but it means the cost of a feature is only really visible once it’s in production and traffic finds it. It’s a familiar pattern in Firebase communities: an inefficient listener, a missing index, or a feature that goes viral for the wrong reasons can turn a predictable few-hundred-dollar month into a bill an order of magnitude higher, with little warning until the invoice arrives.
Supabase’s pricing is built around compute and storage tiers rather than per-operation billing, which makes it substantially easier to forecast costs as a product scales — and to catch a runaway query before it becomes a runaway bill.
SQL and Row Level Security “out of the box”
Firebase security is enforced through a separate rules language (Firestore Security Rules) that lives outside your schema and has to be kept in sync with it by hand as the app evolves. Supabase takes a different approach: Row Level Security (RLS) policies are written in SQL, live directly on the Postgres tables they protect, and are enforced by the database itself — not by a middleware layer your team has to trust blindly.
On top of that, Supabase auto-generates REST and GraphQL APIs directly from your Postgres schema, so a properly modeled, RLS-protected table is immediately queryable from the client without writing a custom backend layer for it.
What actually happens during a Firebase to Supabase migration
Data: reshaping Firestore documents into Postgres tables
There’s no 1:1 mapping between a Firestore collection and a Postgres table, and that’s the part of the migration that actually takes engineering judgment. Each collection needs a decision: does it become its own table, a foreign-keyed child table, or a JSONB column for genuinely flexible, schema-less fields? Subcollections, arrays of references, and denormalized copies of data all need to be resolved into a coherent relational schema before a single row gets migrated.
In practice, this means exporting Firestore data (typically via the Firebase Admin SDK or a batch export), transforming it against the new schema, and bulk-loading it into Postgres — then validating record counts and referential integrity before cutting traffic over.
A concrete example makes this less abstract. A typical Firestore orders document, denormalized the way Firestore encourages, looks something like this:
{
"orderId": "ord_8f21",
"customerName": "Maria Kovalenko",
"customerEmail": "maria@example.com",
"items": [
{ "sku": "SKU-100", "name": "Wireless Mouse", "qty": 2, "price": 19.99 },
{ "sku": "SKU-204", "name": "USB-C Hub", "qty": 1, "price": 34.50 }
],
"status": "shipped",
"createdAt": "2026-06-12T10:04:00Z"
}
Customer details are copied directly into the order because Firestore has no native join — if customerName changes, every past order document with the old name stays stale unless you write a background job to fix it. The equivalent Postgres schema removes that duplication entirely:
create table customers (
id uuid primary key default gen_random_uuid(),
name text not null,
email text unique not null
);
create table orders (
id uuid primary key default gen_random_uuid(),
customer_id uuid references customers(id),
status text not null,
created_at timestamptz not null default now()
);
create table order_items (
id uuid primary key default gen_random_uuid(),
order_id uuid references orders(id) on delete cascade,
sku text not null,
name text not null,
qty integer not null,
price numeric(10,2) not null
);
Three tables instead of one denormalized document, a real foreign key relationship, and no background job needed to keep customer data in sync. This is the kind of decision that has to be made collection by collection — and it’s the actual engineering work in a Firebase to Supabase migration, not the data transfer itself.
Translating Firestore Security Rules into Postgres RLS policies
The mental shift here is worth spelling out, because it changes how access control is written, not just where. A common Firestore rule looks like this:
match /orders/{orderId} {
allow read: if request.auth.uid == resource.data.ownerId;
}
The equivalent Supabase RLS policy is SQL, attached directly to the table:
alter table orders enable row level security;
create policy "Users can read their own orders"
on orders for select
using (auth.uid() = customer_id);
Functionally similar, but the enforcement point has moved from an external rules engine to the database itself — which means every client (your app, a script, an admin dashboard, a future integration) gets the same protection automatically, instead of needing to independently respect the same rules logic.
Auth: preserving users and password hashes
Supabase publishes an official Firebase Auth migration path, including an import script that carries over user records and — for compatible authentication methods — existing password hashes, so end users aren’t forced to reset their passwords the moment you cut over. Users who signed in via Google, Apple, or another OAuth provider migrate even more cleanly, since there’s no password hash to carry over at all — just the account record and provider linkage.
Storage and security rules
Files in Firebase Storage move over to Supabase Storage buckets in a fairly direct transfer. The part that needs actual rework is the security layer: Firebase Storage Security Rules have to be rewritten as Supabase Storage RLS policies. The upside is that once that’s done, storage access control uses the exact same policy language and mental model as your database access control — one system instead of two.
Realtime listeners
Firestore’s onSnapshot listeners and Supabase Realtime solve the same problem — pushing live updates to clients — through fundamentally different mechanisms. Firestore uses its own sync protocol; Supabase Realtime is built on Postgres’s logical replication (change data capture). That difference means realtime subscription code on the client needs to be rewritten against Supabase’s API, not just repointed at a new URL.
Cloud Functions and backend glue code
Firebase Cloud Functions fall into two rough categories, and they migrate very differently. HTTP-triggered functions — the ones called directly from your client or a webhook — map fairly cleanly onto Supabase Edge Functions, which also run on-demand, close to the database, without you managing a server. The rewrite is mostly a change of runtime conventions, not of logic.
Firestore-triggered functions — the ones that fire automatically onCreate, onUpdate, or onDelete for a document — don’t have a direct equivalent, because that trigger model is specific to Firestore. The Postgres way to get the same behavior is a database trigger calling a function, or a Database Webhook that fires on row changes and calls an Edge Function or external endpoint. It’s the same outcome, but it has to be deliberately rebuilt rather than ported.
How to validate a migration before cutover
A migration that “looks done” and a migration that’s actually safe to cut traffic over to are two different things. The validation step is where that gap gets closed, and skipping it is how silent data loss makes it to production. A reasonably thorough validation pass covers:
- Row count reconciliation — every migrated table’s row count matches the source collection’s document count, accounting for any intentional filtering or splitting.
- Spot-check integrity — pull a random sample of records from both systems and compare field-by-field, not just record counts. Aggregate counts can match while individual records are subtly wrong.
- Referential integrity checks — confirm every foreign key actually resolves (no orphaned
order_itemsrows pointing at anorder_idthat doesn’t exist). - Shadow-read testing — for a window before cutover, run the same read query against both Firestore and Supabase from the application layer and log any mismatches, without serving the Supabase result to users yet.
- Dual-write monitoring — during a staged cutover, writes go to both systems and get compared continuously, so a discrepancy surfaces before the old system is switched off, not after.
None of this is exotic engineering — it’s the same discipline any production data migration needs, regardless of source or destination. It’s just easy to skip under deadline pressure, which is exactly when it matters most.
How long does a migration actually take
Complexity, not data volume, is the real driver of migration timelines. As a rough guide, based on the pattern of projects we scope at Gart Solutions:
| Project size | Typical scope | Rough timeline |
|---|---|---|
| Small | Few collections, mostly flat documents, OAuth-heavy auth, tolerant of a maintenance window | 1–2 weeks |
| Medium | Moderate relational structure, mixed password/OAuth auth, some custom Cloud Functions, staged cutover required | 3–6 weeks |
| Large | Deep relational data, heavy Security Rules logic, high-traffic production system, zero-downtime requirement | 6–12+ weeks |
These ranges assume the schema design work happens up front rather than mid-migration — which is the single biggest lever on whether a project lands in the low or high end of its bracket.
How complex is a Firebase to Supabase migration, really?
| Component | Complexity | Why |
|---|---|---|
| Data model | Moderate–High | No 1:1 mapping from Firestore collections to Postgres tables; requires real schema design |
| Authentication | Low–Moderate | Official export/import tooling exists; password-based users need hash migration, OAuth users migrate cleanly |
| Storage | Low | Files transfer directly; security rules need to be rewritten as RLS policies |
| Realtime | Moderate | Different underlying mechanism (sync protocol vs. Postgres replication); client code needs rework |
Taken together, the honest answer is: moderate. The data migration from Firestore’s NoSQL model into a relational schema is the piece that needs the most thought, while authentication is comparatively well-trodden ground thanks to Supabase’s official scripts. Neither part is trivial, but neither is a research project — this is a well-understood migration path with documented tooling on both ends, not an unmapped one.
Common pitfalls we see
- Treating it as a lift-and-shift. A Firebase to Supabase migration is a data modeling project first and a data moving project second. Skipping the schema design step to move faster usually means redoing it later, under worse conditions.
- Underestimating how much logic lives in Security Rules. Firestore rules often quietly encode business logic, not just access control. That logic needs to be found and accounted for, not just translated line by line.
- No staged cutover plan. Migrating a live, actively-used database without a dual-write window or a clear rollback point is how “weekend migration” turns into “week-long incident.”
- Ignoring what your composite indexes are telling you. If Firestore needed a composite index to make a query work, that’s usually a preview of exactly where your new Postgres schema needs a proper join.
A quick pre-migration checklist
Before scoping a Firebase to Supabase migration, it’s worth answering these questions honestly — they tend to predict how smooth the project will be far better than data volume does:
- How many collections have deep relational structure (references to other collections, denormalized copies of data) versus how many are genuinely flat, self-contained documents?
- How much business logic is embedded in Firestore Security Rules, beyond simple “is this user the owner” checks — conditional writes, rate limiting, cross-document validation?
- What percentage of your users authenticate via email/password versus OAuth providers? This directly affects how much of your Auth migration is a clean import versus a hash-compatibility exercise.
- Can the application tolerate a staged cutover, or does it need a hard switch at a specific moment? This shapes whether a dual-write window is realistic or whether you’re planning around a maintenance window instead.
- Are there Cloud Functions or other Firebase-specific glue code that will need to be reimplemented as Supabase Edge Functions or moved elsewhere entirely?
None of these questions have a “wrong” answer — they just change the shape of the migration plan. A team with mostly flat collections, mostly OAuth users, and tolerance for a staged cutover is looking at a materially simpler project than one with deep relational data, password-heavy auth, and a hard cutover requirement. Scoping this honestly up front is what keeps a migration on schedule.
Planning a production migration, not a side project?
Gart Solutions runs zero-downtime cloud migrations for teams moving off Firebase and onto Supabase — including schema design, Auth cutover, and RLS setup.
How Gart Solutions approaches these migrations
A Firebase to Supabase migration touches data modeling, authentication, security policy, and often the client application itself — which is exactly why we treat it as an engineering project with a plan, not a weekend script. Our process typically covers:
- Schema design — turning Firestore collections into a properly normalized (or deliberately denormalized, where it makes sense) Postgres schema
- Auth migration — carrying over users and password hashes where possible, with a clear path for OAuth-based accounts
- Row Level Security — building the RLS policy set that replaces Firestore Security Rules, table by table
- Zero-downtime cutover — staged migration and dual-write windows for production systems that can’t afford an outage
Just as important: we handle migrations of any level of complexity, not only from Firebase. The same team that plans a Firestore-to-Postgres schema redesign also runs migrations from Amazon RDS, Neon, Heroku, Render, and legacy MySQL or MSSQL databases onto Supabase — and, where it’s the right call for a client, migrations away from Supabase too. If your source system isn’t Firebase, the underlying question is usually the same one: what’s the safest path from where your data is now to where it needs to be, and how do you get there without breaking production.
Conclusion
Firebase gets you moving fast, and for plenty of products, it stays the right choice for a long time. But once your data has real relationships, your bill has real traffic behind it, and your team wants access control that lives in the database rather than a parallel rules layer, a Firebase to Supabase migration starts to look less like an experiment and more like a fix. The migration itself is moderate in complexity, well-documented on the tooling side, and — with the right schema design up front — doesn’t have to touch production uptime at all.
Planning a Supabase or Postgres migration?
Gart Solutions’ database migration engineers plan and execute Postgres migrations — into Supabase, within a live schema, or off a managed platform entirely — with a rollback plan built in from day one, not bolted on after something breaks.
See how we can help to overcome your challenges


