Skip to content

Migrating from Firebase to Supabase: The Real Cost and the Step-by-Step Plan

Firebase pricing scales worse than Supabase past a certain point. Here’s the migration plan that worked for one of our clients.

John Cravey with EleviFounder4 min read

We migrated a client app from Firebase to Supabase last quarter. The driver was cost — Firebase Firestore reads at the scale they were operating ($580/month) compared to Supabase Pro flat-rate ($25/month) didn’t require a spreadsheet to make the case. The migration took six weeks of focused work. Here’s the plan we used and the gotchas we hit, so you can do it faster if you’re considering the same move.

Why someone would do this

  • Firestore read pricing scales linearly with usage. Once you hit ~10M reads/month, you’re paying more than a Supabase Pro plan covers.
  • Firestore’s query language is limited — no real joins, no aggregation, no full-text search without a separate Algolia/Elastic.
  • Firebase Auth has fewer customization options than Supabase Auth.
  • Postgres opens doors: window functions, recursive queries, GIS, vector search via pgvector, anything you’d expect from a real RDBMS.

Why someone wouldn’t

  • Firebase’s mobile SDK is best-in-class. If you’re primarily mobile, Supabase’s mobile SDKs are good but not yet ahead.
  • Firebase has tightly-integrated services (App Check, Remote Config, A/B Testing). Replacing them piecemeal costs time.
  • If your data model is genuinely document-shaped (deeply nested, schemaless), Postgres’s jsonb columns work but feel awkward.

Step 1: data model translation

Map every Firestore collection to a Postgres table. Map every document field to a column. Nested objects either become jsonb columns or get normalized into separate tables. The mapping is the most time-consuming part — plan a day per substantial collection.

Step 2: data export and import

Firebase has a built-in export to Google Cloud Storage. Export to JSON, then write a Node script that streams the JSON into Supabase via the JS client (using the service-role key). For the client we migrated, 4M documents took 3 hours to import.

import { createReadStream } from "node:fs";
import { parse } from "JSONStream";
import { createClient } from "@supabase/supabase-js";

const admin = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!);

const stream = createReadStream("./firestore-export/orders.json")
  .pipe(parse("*"));

let batch: any[] = [];
for await (const doc of stream) {
  batch.push(translateDoc(doc));
  if (batch.length >= 500) {
    await admin.from("orders").insert(batch);
    batch = [];
  }
}
if (batch.length) await admin.from("orders").insert(batch);

Step 3: auth migration

Firebase Auth users export with their UIDs and email addresses. Supabase Auth can be primed with these via the admin API. Passwords don’t transfer — you’ll need to send everyone a password-reset email or move to magic-link login. We used the migration as an excuse to switch to magic-link.

Step 4: replace Firestore queries with Postgres

Every Firestore `.where().orderBy().limit()` chain becomes a Postgres SELECT. The Supabase JS client mirrors the chained-method style, so the translation is largely mechanical. The places where it isn’t are usually queries that were complicated workarounds for Firestore’s limitations — those simplify dramatically in SQL.

// Firestore
const q = query(
  collection(db, "orders"),
  where("userId", "==", uid),
  orderBy("createdAt", "desc"),
  limit(50)
);

// Supabase
const { data } = await supabase
  .from("orders")
  .select("*")
  .eq("user_id", uid)
  .order("created_at", { ascending: false })
  .limit(50);

Step 5: replace Cloud Functions with Edge Functions or server-side code

Firebase Cloud Functions either translate to Supabase Edge Functions (for webhook receivers, scheduled jobs) or to Next.js API routes / server actions (for tenant-coupled logic). Most of the client’s Cloud Functions had grown into tightly-coupled logic that moved better into the Next app.

Step 6: Storage migration

Firebase Storage paths can be re-uploaded into Supabase Storage one bucket at a time. We wrote a script that streamed each file from Firebase to Supabase. Total time on the migration: 6 hours for 12GB across 4 buckets. The URLs change, so update every reference in the app code.

Step 7: dual-write phase

For two weeks before cutover, the app wrote to both Firebase and Supabase. Reads stayed on Firebase. This caught translation bugs where the Supabase data didn’t match Firebase. We resolved 23 mismatch types during dual-write.

Step 8: cutover

Reads switch to Supabase, writes continue to both. Monitor for a week. Then writes cut over too. Firebase becomes read-only as a safety net for another 30 days. Then Firebase project is deleted.

Gotchas we hit

  • Firestore’s server timestamps don’t map cleanly to Postgres timestamptz. We had a 4-hour bug where new rows were timestamped in UTC but the app expected the tenant’s local time zone.
  • Firestore’s array-contains queries map to Postgres array operators awkwardly. Two queries had to be rewritten as separate JOIN tables.
  • Firebase Auth UIDs are random strings; Supabase Auth UIDs are UUIDs. Migrating users meant rewriting every foreign key from string-UID to UUID. We added a temporary `firebase_uid` column to map old to new during the dual-write phase.
  • Firebase rules don’t translate to RLS policies one-to-one. We had to rewrite the whole authorization layer; in retrospect it was an improvement.

Cost outcome

The client moved from $580/month on Firebase to $25/month on Supabase Pro. Read volume is identical. Write volume is identical. The savings paid for the migration work in five months and continues to compound. The performance is slightly better on Supabase (P95 query time down 40ms) because Postgres indexing is more aggressive than Firestore’s.

When NOT to migrate

If you’re under 1M Firestore reads/month, the cost difference isn’t worth the migration time. If your data model is genuinely deeply-nested and schemaless, Postgres works but it’s not a natural fit. If you’re heavy on Firebase-specific services (App Check, Remote Config), the replacement effort multiplies.

How this lands at FH

We’ve done one Firebase-to-Supabase migration so far. The decision framework holds: cost-driven, query-flexibility-driven, or auth-customization-driven. If you’re running Firebase and curious whether the migration is worth it for you, book a consultation — we’ll quote a fixed-scope migration sprint if the math works in your favor.

Written by
John Cravey
Founder

Founder of Frontend Horizon. Writes most of the long-form work on the FH blog.

Newer post
Tool Use With Claude: Building Agents That Don’t Hallucinate Your Production Data
Older post
Reducing Third-Party Script Weight: The Audit Pattern That Saves Half Your JS Budget
Keep reading

More from the blog

Supabase·6 min

Supabase Row Level Security: The Multi-Tenant Pattern We Use Across FH Clients

One Postgres database, many tenants, zero data leakage. Here’s the RLS setup that holds up under real production traffic.

Supabase·5 min

Supabase Edge Functions: When They’re Worth It and When They’re Not

Edge Functions are great for jobs that have to live outside your Next app. Not everything does. Here’s the decision framework.

Supabase·3 min

Reading Supabase Logs: The Five Queries That Catch 80% of Production Issues

The Supabase log explorer is underused. These five queries are the first place we look when something’s wrong.