tutorial··5 min read·WoWSQL Team

How to Migrate Firebase to Postgres: Step-by-Step Guide

A step-by-step guide to migrate Firebase to Postgres: model Firestore data as tables, export documents, move users and files, and switch your app safely.

To migrate Firebase to Postgres, you do five things in order: design relational tables for your Firestore collections, export the documents with the Firebase Admin SDK, load them into Postgres, move your users and files, and then switch the app over behind a short read-only window. None of it is magic, but with a plan most small and midsize apps can move in a few days. This guide walks through each step using WoWSQL, a managed Postgres backend with auth, storage, realtime and a generated REST API, hosted in Mumbai or US East 1.

If you're still deciding whether to move at all, start with our Postgres vs Firestore comparison. This post assumes you've decided and want the how.

Key facts

  • Firestore managed export (checked on Google Cloud docs, 30 October 2026): gcloud firestore export gs://[BUCKET_NAME] needs billing enabled, which means the Firebase Blaze plan, and writes to a Cloud Storage bucket.
  • Firebase Auth export (checked on Firebase docs, 30 October 2026): firebase auth:export writes users to JSON or CSV, and only exports passwords hashed with Firebase's scrypt variant.
  • WoWSQL: managed PostgreSQL with auth, storage, realtime, REST and MCP APIs, in two regions: Mumbai (the default) and US East 1.
  • WoWSQL pricing (wowsql.com/pricing, 30 October 2026): Free at $0 (256 MB database, 512 MB files), Pro at $25 a month (10 GB database, daily backups with 7-day retention), Scale at $99 a month.
  • SDK: new WowSQLClient({ projectUrl, apiKey }).table('orders') from @wowsql/sdk 3.10.0.
  • Company: WoWSQL is built in India, DPIIT-recognised and part of NVIDIA Inception.

Step 1: Model Firestore collections as Postgres tables

This is the step that decides whether the migration feels easy or painful. Firestore stores JSON-like documents in collections and subcollections. Postgres stores rows in tables with fixed columns and relationships between them.

A good rule of thumb:

  • Each top-level collection becomes a table. users, orders, products.
  • Each subcollection becomes its own table with a foreign key. users/{uid}/addresses becomes an addresses table with a user_id column.
  • Duplicated (denormalised) fields go away. In Firestore you often copied a user's name into every order. In Postgres you store it once and join.
  • Maps you query on become columns. Maps you only read whole can stay as a jsonb column.
  • Keep the old document ID. Store it in a firebase_id text unique column so you can match records during the move and debug later.

Here's what a typical shop schema looks like after the mapping:

create table customers (
  id bigserial primary key,
  firebase_id text unique not null,
  email text unique,
  name text,
  created_at timestamptz default now()
);

create table orders (
  id bigserial primary key,
  firebase_id text unique not null,
  customer_id bigint references customers(id),
  status text not null default 'pending',
  total numeric(12,2) not null,
  items jsonb not null default '[]',
  created_at timestamptz not null
);

create index on orders (customer_id, created_at desc);

Run this in the WoWSQL SQL editor. Firestore timestamps become timestamptz, numbers become numeric or bigint, and document references become foreign keys.

Step 2: Export your Firestore data

Firestore offers a managed export with gcloud firestore export, but Google's docs say it needs billing enabled (the Blaze plan) and it writes to a Cloud Storage bucket in Firestore's own export format, not plain JSON. For a migration, a short Admin SDK script that writes JSON is usually simpler:

// export.mjs — run with a Firebase service account
import { initializeApp, cert } from 'firebase-admin/app';
import { getFirestore } from 'firebase-admin/firestore';
import fs from 'node:fs';

initializeApp({ credential: cert('./service-account.json') });
const db = getFirestore();

const snap = await db.collection('orders').get();
const rows = snap.docs.map((d) => ({ id: d.id, ...d.data() }));
fs.writeFileSync('orders.json', JSON.stringify(rows, null, 2));
console.log(`Exported ${rows.length} orders`);

For large collections, page through with orderBy and startAfter instead of reading everything at once. Remember that reads count against your Firestore quota, so run the export once, against a quiet period.

Step 3: Load the data into Postgres

Now transform each document into a row and insert it. Use the WoWSQL service role key on a server or your laptop only, never in client code, because it bypasses row-level security.

import WowSQLClient from '@wowsql/sdk';
import fs from 'node:fs';

const client = new WowSQLClient({
  projectUrl: 'myproject',                 // https://myproject.wowsqlconnect.com
  apiKey: process.env.WOWSQL_SERVICE_KEY!,
});

const orders = JSON.parse(fs.readFileSync('orders.json', 'utf8'));

for (const o of orders) {
  await client.table('orders').create({
    firebase_id: o.id,
    status: o.status ?? 'pending',
    total: o.total,
    items: o.items ?? [],
    created_at: o.createdAt?._seconds
      ? new Date(o.createdAt._seconds * 1000).toISOString()
      : new Date().toISOString(),
  });
}

Load parent tables first (customers), then children (orders), looking up customer_id by firebase_id. Afterwards, compare row counts against your export and spot-check a few records. Each SDK call is a standard PostgREST request to /rest/v1, which our instant REST API for Postgres guide explains in detail.

Step 4: Move users and files

Users. firebase auth:export users.json --format=JSON gives you emails, UIDs and provider data. Firebase's docs note that password hashes use a modified scrypt, and moving those hashes into another auth system isn't something WoWSQL documents. The safe plan is to recreate users in WoWSQL Auth by email, keep a mapping from Firebase UID to the new user ID, and ask email and password users to reset their password on first sign-in. Users who signed in with Google or another OAuth provider simply sign in again. Our Postgres authentication guide covers the auth flows.

Files. Copy objects out of Cloud Storage for Firebase with gcloud storage cp -r, upload them to WoWSQL Storage, and update the file paths stored in your tables. The Free plan includes 512 MB of files and Pro includes 100 GB.

Step 5: Replace realtime listeners and Cloud Functions

Firestore's onSnapshot listeners map to Postgres change events. WoWSQL Realtime lets you subscribe to inserts, updates and deletes on a table through client.realtime.channel(...); the Postgres realtime guide shows the full pattern.

Cloud Functions triggers need a new home. Move them to Edge Functions on the Pro or Scale plan, or to your own small server. Security rules become row-level security policies in Postgres, which you write once in SQL.

Step 6: Cut over without losing writes

  1. Run a full export and load into WoWSQL, and test the new app against it.
  2. Pick a quiet window and put the Firebase app into read-only mode.
  3. Export only documents changed since the first run, using an updatedAt field if you have one, and load them.
  4. Ship the app build that points at WoWSQL.
  5. Keep Firebase read-only for a week or two before deleting anything.

FAQ

How long does it take to migrate Firebase to Postgres?

It depends on how many collections you have and how much logic lives in Cloud Functions. A small app with a handful of collections can often move in a few days. Schema design and testing take most of the time, not the data copy.

Can I export Firestore without the Blaze plan?

Google's managed export needs billing enabled, which moves the project to Blaze. Reading collections with the Firebase Admin SDK and writing JSON works without the managed export service, but those reads still count against your Firestore usage.

Do my users need to reset their passwords?

Usually, yes, for email and password accounts. Firebase exports scrypt hashes, and importing them into WoWSQL Auth isn't documented, so plan a password reset. OAuth users just sign in again with the same provider.

Should I store Firestore documents as jsonb?

Only for parts you don't filter or join on. Putting everything in one jsonb column gives up most of Postgres's benefits. Turn queried fields into real columns and keep jsonb for flexible extras.

How much does WoWSQL cost after the move?

WoWSQL has a Free plan at $0, Pro at $25 a month and Scale at $99 a month, priced in US dollars on wowsql.com/pricing (checked 30 October 2026). You can start free without a credit card and compare other options in our best Supabase alternatives roundup.