A database is not a Firebase replacement. That sentence is the reason this repository exists, and it belongs at the top rather than buried in a caveats section at the bottom.
Firebase is a bundle: Firestore or Realtime Database for storage, Authentication, client SDKs that keep a local cache in sync over a live connection, Hosting, Cloud Functions, Cloud Messaging, Analytics, Crashlytics. Search results for "free Firebase alternative" mostly return databases. A database replaces one item on that list. Everything else is work you would be picking up.
What follows is a decision aid first and a migration toolkit second. If you read only one section, read the one arguing you should stay.
| Firebase piece | What it does for you | What a plain database gives you instead |
|---|---|---|
| Firestore / RTDB | Document storage | Document storage. This is the one that maps. |
Realtime listeners (onSnapshot) |
Server pushes changes to every connected client | Nothing. You build it. |
| Offline persistence | Local cache, queued writes, automatic replay on reconnect | Nothing. You build it. |
| Security Rules | Per-document authorisation enforced server-side, so clients can talk to the database directly | Nothing. You put an API in front and authorise there. |
| Authentication | Identity, providers, sessions, token refresh | Nothing. Separate concern, separate product. |
| Hosting / Functions | Static hosting, serverless compute | Nothing. Separate concern. |
| Analytics / Crashlytics | Product and crash telemetry | Nothing. Separate concern. |
Last verified: 2026-08-18. Product surfaces change — check firebase.google.com/docs rather than trusting this table indefinitely.
If your honest answer is "I only use Firestore as a database, my server talks to it, my clients talk to my server" — then row one is your whole migration and the scripts here will do most of it.
If your answer involves onSnapshot in a mobile app with a spotty connection,
keep reading, but adjust your estimate upward by a lot.
Reasons to stay on Firebase, stated as specifically as I can manage:
- Your clients talk to Firestore directly. Firebase's model is that the browser or phone holds a credential and Security Rules decide what it may read and write. Take Firestore out and you need a server between the client and the data, with authentication, authorisation, input validation and rate limiting — because the alternative is a database with an open port and no rules engine. That server is weeks of work and a permanent maintenance obligation. It is not one afternoon.
- You rely on offline mode. The Firestore SDK caches locally, serves reads from the cache when the network is gone, queues writes, and replays them on reconnect. Reimplementing that correctly means conflict resolution, and conflict resolution is a research problem with a long history of subtly wrong implementations. Mobile apps in particular should think very hard here.
- The free tier is working for you. Firebase's Spark plan has real free quotas and a lot of projects never leave them. A migration to escape a bill you are not receiving is pure cost.
- You use Firebase Auth. It is genuinely good, it is not the database, and it does not have to move just because the data does. If you migrate the data and keep Auth, you now operate two systems and a token-verification path between them. That is sometimes right and it is never free.
- Someone told you Firestore "does not scale" or "locks you in." Both claims are overstated. Firestore scales further than most applications ever need, and your data comes out — the scripts here are proof, and they use Google's own documented Admin SDK to do it. Lock-in in Firebase is about the client SDK behaviour, not the data.
- You are pre-launch. Migrating infrastructure before you have users is the most reliable way to not have users.
The strongest reasons to actually move: you want SQL-shaped or aggregation-heavy queries that Firestore does not do well, you want a database you can point existing tooling at, you have a self-hosting or data-residency requirement, or your architecture is already server-mediated and Firestore is doing nothing that a document store would not.
Someone whose application is already server-mediated — a backend that talks to Firestore with the Admin SDK — and who wants that backend to talk to a plain document store instead. For that reader this is a data migration and the scripts here cover it end to end.
It is also for someone who has not decided yet and wants to see the size of the gap in writing. That is the next section.
Three scripts, run in order, into whatever MongoDB endpoint MONGODB_URI points at —
a free one is enough for a trial run.
Full flags and behaviour in examples/README.md.
pip install firebase-admin pymongo
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
export MONGODB_URI='mongodb://HOST:27017/appdb'
python examples/firestore_dump.py --out ./dump --limit 50 # sample first
python examples/firestore_dump.py --out ./dump # then everything
python examples/import_documents.py --in ./dump --dry-run
python examples/import_documents.py --in ./dump
python examples/verify_import.py --in ./dump --sample 25Why not gcloud firestore export. Google's managed export/import service
requires the project to be on the Blaze plan, and it writes a binary format into
a Cloud Storage bucket intended to be read back by Firestore or loaded into
BigQuery. Neither is helpful if you are on Spark and want documents you can
read. firestore_dump.py uses the Admin SDK instead: works on any plan, costs
one document read per document, and emits JSONL you can inspect with head.
Subcollections. Firestore subcollections hang off documents. A dump of
users contains nothing from users/*/orders unless you go looking, and this
is the single most common way a Firestore migration silently loses half the
data. The dumper finds them with collection group queries and writes them as
users__orders.jsonl.
Types. Timestamps, GeoPoints, DocumentReferences and byte fields are tagged
in the JSONL and decoded on import, so a round trip does not flatten your data
to strings. Anything the dumper could not classify is written as $raw and
reported loudly, because that is exactly the field you need to look at.
Rollback. Nothing in this pipeline writes to Firestore. Undoing the
migration is repointing your backend at Firebase. Undoing the import is
dropping the collections import_documents.py names in its summary; every
write is an upsert keyed on the original document ID, so re-running is safe and
never duplicates.
This is the big one and it deserves more than a bullet.
onSnapshot gives you a persistent connection, server-pushed deltas, automatic
reconnection with resumption, and a local cache that stays consistent — across
every client, on web and mobile, for free, with about four lines of code.
Replacing it means building, roughly:
- A change source. Something that knows a document changed. MongoDB change
streams do this, but they require a replica set — check whether
your target provides one before
designing around it. Otherwise you are polling with a
updatedAtcursor, which means picking a poll interval and living with the staleness and the load it implies. - A transport. WebSockets or SSE, plus a server that holds connections. Then: reconnection with backoff, resumption from a last-seen position so a reconnecting client does not miss changes, and heartbeats.
- A fan-out layer. One process cannot hold every connection forever. Two processes need a bus between them so a write on one reaches subscribers on the other. Redis pub/sub is the usual answer, which is another service.
- Subscription authorisation. Firestore's rules decide what a listener may see. Yours has to as well, on subscribe and on every subsequent push, or you have built a data-leak generator.
- Client-side state reconciliation. Applying deltas to local state in order, handling out-of-order and duplicate delivery, and reconciling after a gap.
That is not a weekend. Teams that have done it will tell you it is a quarter and then an ongoing maintenance surface. If your product's value depends on live collaborative state, this alone is a reason to stay on Firebase.
Firestore's SDK reads from a local cache when offline, queues writes, and replays them on reconnect, resolving against the server's state. Rebuilding it means a client-side store, an outbox, replay ordering, and a conflict policy — last-write-wins is easy to implement and easy to lose data with. Budget for this properly or do not promise offline support.
Rules let untrusted clients hold a database credential safely. Without them the
model has to change: clients talk to your API, your API talks to the database,
and every rule becomes a check in application code. That is a normal
architecture — most of the software in the world works this way — but it is a
different one, and porting a large firestore.rules file into middleware is a
genuine project with a genuine chance of getting an authorisation check wrong.
Do not skip this by exposing a database connection string to a client. There is no version of that which is acceptable.
Auth, Hosting, Functions, Messaging, Analytics, Crashlytics, App Check, Remote Config. None of these are a database's job, and none of them are addressed by anything in this repository. If you use them, they stay where they are or they become separate migrations.
The scripts write to MongoDB, because Firestore documents map onto MongoDB
documents with less distortion than onto anything relational.
Any MongoDB 7 endpoint works;
MONGODB_URI is the only knob.
freebase.cloud is one option for it — MongoDB 7.0.4
over the real wire protocol on port 27017, so mongosh, Mongoose, PyMongo,
Motor and the native driver connect unmodified.
mongosh "mongodb://HOST:27017/appdb"db.users__orders.createIndex({ _parentId: 1, createdAt: -1 })
db.users.find({ "profile.plan": "free" }).limit(5)Free tier, no credit card, aimed at development, prototyping and small production workloads. No published SLA, uptime figure or backup schedule — and since you are considering leaving a Google-operated service, that difference should weigh heavily rather than being read past.
The same instance is reachable over MCP, which is a thing Firestore does not
offer. In Settings → MCP, mint a token against the connection and
take the URL it gives you. With a
connection named store you get store_query, store_store,
store_list_tables and store_annotate_table.
claude mcp add --transport http store https://freebase.cloud/api/mcp/YOUR_TOKENThis is genuinely useful during a migration: after import_documents.py runs,
you can ask an assistant to look at the imported collections and tell you which
fields are inconsistently typed, rather than writing a script per question.
Annotate the collections first — store_annotate_table with "users__orders
came from Firestore subcollections; _parentId is the user id" saves the model
from guessing.
For Claude Desktop, add the URL under Settings → Connectors → Add custom connector; remote HTTP servers are not supported in the desktop config file, and the client-by-client instructions cover the rest. The token sits in the URL path, so treat the whole URL as a secret.
- Export and import Firestore data — Blaze requirement, non-snapshot semantics
- Firestore: access data offline
- Firestore Security Rules
- MongoDB 7.0 manual · change streams
- Free MongoDB cloud instance
- How to connect Claude to MongoDB
- Engine list and sign-up
freebase.cloud is an independent service and is not affiliated with Google LLC, Firebase, MongoDB, Inc., Redis Ltd. or Anthropic, PBC.