Why sync Firebase to PostgreSQL
Firestore queries are fast and simple by design: no joins, no aggregations beyond counts and sums, and indexes you plan up front. Once a product has customers, teams want SQL:
- Reporting and BI. Metabase, Looker Studio, Grafana, Hex and most BI tools speak Postgres, not Firestore.
- Joins. Combine app data with billing, CRM or support tables in one query.
- Migration. Moving off Firebase is far safer when Postgres already holds a live, verified copy.
- Ad-hoc questions. Engineers and analysts can answer them without writing scripts against the Admin SDK.
Four ways to get Firestore data into Postgres
- Export and import.
gcloud firestore exportwrites LevelDB files to Cloud Storage; a script converts and loads them. Fine for a one-off copy. It's a snapshot, and every new export reads every document again. - Cloud Functions triggers. An
onDocumentWrittenfunction writes each change to Postgres. Real time, but it's code you own: you also need a backfill, retries, ordering, deletes, and a trigger per collection path, including nested ones. - Batch ETL tools. Scheduled syncs that usually re-read collections on each run because Firestore has no change cursor, so cost grows with data size, and deletes and subcollections need checking.
- Change streaming (Fireloom). Listens to Firestore's change stream, applies inserts, updates and deletes to Postgres within seconds, and resumes from checkpoints after restarts. Nothing to deploy.
A side-by-side table is on the comparison page.
The table you get
Fireloom writes every document, from every collection at every depth, into one table. Collection names never create tables, so any naming scheme works and nothing breaks when the app adds a field.
Firestore types map to JSON without losing information: timestamps become RFC 3339 strings, GeoPoints become {lat, lng}, references become document paths, and bytes become base64.
Querying it
Filter by collection, read fields with ->>, and join a subcollection to its parent with parent_path:
For heavier use, add a view per collection and expression indexes on the fields you filter by, for example CREATE INDEX ON firestore.documents ((data->>'status')) WHERE collection = 'orders'.
Set it up in about five minutes
- Connect Firestore. Run one command in your Firebase project's folder. It creates a service account that can read Firestore (and export it for the first load) but never write to it, and hands its key to Fireloom.
- Connect Postgres. Paste a connection string for Supabase, Neon, RDS, Cloud SQL or any reachable Postgres. Fireloom creates the
documentstable in a schema you choose. - Go live. Pick collections or take everything. Fireloom loads every document once, then streams every change.
What it costs in Firestore reads
Firestore bills one read per document returned. The first load reads each document once (an export costs the same). Afterwards Fireloom reads only documents that change, resumes from checkpoints after restarts instead of re-reading, and runs a daily consistency check that costs about one read per 1,000 documents. Details are in the sync engine page.
Limits worth knowing
- A subcollection with a brand-new name is picked up at the next daily consistency check, or right away with Resync. New documents in known collections, however deep, stream immediately.
- Fields live in
jsonb. Typed columns are on the roadmap; views cover most needs today. - Sync is one way, Firestore to Postgres. Fireloom never writes to Firestore.
Try it on your own data
Free up to 100,000 documents per Firebase project. No credit card.
Start syncing free