Migrating from MongoDB to FerretDB
If you've read our MongoDB vs FerretDB comparison, you know FerretDB implements the MongoDB wire protocol on top of PostgreSQL. Same mongodb:// connection string, same driver, Apache 2.0 licensed. But knowing that FerretDB exists and actually migrating a production app to it are two different things.
Unlike Redis to Valkey, where you literally swap the connection string and everything works, MongoDB to FerretDB has real compatibility gaps. FerretDB covers a large subset of the MongoDB API, but it is not a complete drop-in. The migration is feasible for many applications. You just need to check your queries first.
Contents
- What Works Out of the Box
- What Doesn't Work Yet
- Check Your Codebase
- Set Up FerretDB with SpinDB
- Migrate Your Data
- Point Your App at FerretDB
- The PostgreSQL Bonus
- When to Stay on MongoDB
- Wrapping Up
What Works Out of the Box
FerretDB supports the core operations that most applications rely on. If your codebase uses standard CRUD and basic aggregation, the migration is straightforward.
CRUD operations:
insertOne,insertManyfind,findOnewith projections and sortingupdateOne,updateManywith$set,$unset,$inc,$push,$pull,$addToSetdeleteOne,deleteManyreplaceOne
Query operators:
- Comparison:
$eq,$ne,$gt,$gte,$lt,$lte,$in,$nin - Logical:
$and,$or,$not,$nor - Element:
$exists,$type - Array:
$elemMatch,$size,$all - Regex:
$regex
Aggregation stages:
$match,$group,$sort,$project,$unwind$limit,$skip,$count
Other:
- Array queries (matching elements, nested arrays)
- Dot notation for nested documents
- Index creation (
createIndex) countDocuments,estimatedDocumentCountdistinct
This covers the majority of what typical CRUD applications use. If your app inserts documents, queries them with filters, runs some aggregation for reporting, and updates records, you're likely in the clear.
What Doesn't Work Yet
Here is where you need to pay attention. These MongoDB features are not supported in FerretDB as of early 2026:
| Feature | MongoDB | FerretDB | Workaround |
|---|---|---|---|
$lookup | Cross-collection joins in aggregation | Not supported | Two queries joined in application code, or query PostgreSQL directly |
$graphLookup | Recursive graph traversal | Not supported | Application-level recursion, or PostgreSQL recursive CTEs |
| Change streams | Real-time document change notifications | Not supported | PostgreSQL LISTEN/NOTIFY |
$text search | Built-in text indexes and search | Not supported | PostgreSQL tsvector/tsquery, or a search engine like Meilisearch |
| Capped collections | Fixed-size, insertion-order collections | Not supported | Application-level cleanup, or PostgreSQL partitioning with retention policies |
$merge / $out | Write aggregation results to a collection | Not supported | Fetch results in app code and insert into target collection |
| Schema validation | $jsonSchema on createCollection | Not supported | Application-level validation, or PostgreSQL constraints on the underlying table |
| Time-series collections | Optimized time-series storage | Not supported | PostgreSQL with TimescaleDB extension, or a purpose-built time-series DB like QuestDB or InfluxDB |
| Atlas Search | Full-text and vector search (Atlas only) | Not supported | PostgreSQL full-text search, pgvector, or dedicated search engines |
The FerretDB team publishes a compatibility page that tracks supported commands and operators. Bookmark it and check before migrating.
Check Your Codebase
Before you touch any infrastructure, grep your codebase for unsupported operations. This takes five minutes and tells you exactly how feasible the migration is.
# Search for unsupported MongoDB features
grep -rn '\$lookup' src/
grep -rn '\$graphLookup' src/
grep -rn '\.watch(' src/
grep -rn '\$text' src/
grep -rn '\$merge' src/
grep -rn '\$out' src/
grep -rn 'capped.*true' src/
grep -rn '\$jsonSchema' src/If none of those return results, your migration path is clean. Swap the connection string, run your test suite, and you're done.
If they do return results, you need to assess each usage:
$lookupin a few places? Refactor to two queries joined in your application code. It's more code, but it works identically on both engines.- Change streams for cache invalidation? Switch to polling, or use PostgreSQL
LISTEN/NOTIFYafter migrating. $textfor search? This is often the hardest one to replace inline. Consider adding Meilisearch or using PostgreSQL full-text search on the underlying tables.
The key question: are the unsupported features load-bearing in your application, or are they convenience features you can work around?
Set Up FerretDB with SpinDB
The fastest way to test the migration locally is SpinDB. One CLI, no Docker, no manual configuration. (What is SpinDB?)
Install SpinDB:
npm i -g spindb # npm
pnpm add -g spindb # pnpmCreate a FerretDB instance:
spindb create ferret-migration -e ferretdb --startSpinDB downloads FerretDB, provisions the PostgreSQL backend, and starts the server. Get the connection URL:
spindb url ferret-migrationmongodb://127.0.0.1:27018That's a standard mongodb:// URL. Your existing MongoDB driver connects to it without any code changes.
Migrate Your Data
Use mongodump to export your data from MongoDB and mongorestore to import it into FerretDB. These are standard MongoDB tools, and FerretDB supports the wire protocol they use.
Step 1: Dump from MongoDB
mongodump --uri="mongodb://localhost:27017" --db=myapp --out=./dumpThis creates a ./dump/myapp/ directory with BSON files for each collection.
If your MongoDB instance requires authentication:
mongodump --uri="mongodb://user:pass@your-mongo-host:27017/myapp?authSource=admin" --out=./dumpStep 2: Restore into FerretDB
mongorestore --uri="mongodb://localhost:27018" --db=myapp ./dump/myapp/FerretDB accepts the same mongorestore protocol. Your documents get stored as JSONB in the underlying PostgreSQL database.
Step 3: Verify the data
Connect with mongosh and spot-check:
mongosh "mongodb://localhost:27018/myapp"db.users.countDocuments()
db.users.findOne()
db.orders.countDocuments()Compare the counts and a few sample documents against your MongoDB source. If they match, your data is in FerretDB.
Point Your App at FerretDB
Update your connection string. That's it. Your MongoDB driver doesn't know or care that the backend changed.
// Before
const MONGO_URL = 'mongodb://localhost:27017/myapp'
// After
const MONGO_URL = 'mongodb://localhost:27018/myapp'Now run your test suite:
pnpm testIf all tests pass, you're migrated. If some tests fail, the failures will point you to exactly which MongoDB features you're using that FerretDB doesn't support yet.
This is why running tests is the real migration step. The data transfer is mechanical. The compatibility check is what matters.
The PostgreSQL Bonus
Once your data is in FerretDB, it lives in PostgreSQL. This unlocks things MongoDB can't offer.
Query with SQL. Your documents are stored as JSONB. You can query them with standard SQL:
SELECT _jsonb->>'title' AS title, _jsonb->>'status' AS status
FROM myapp.tasks_aaa30e43
WHERE _jsonb->>'priority' = 'high';The table names are auto-generated by FerretDB, but you can find them with \dt in psql.
Use pg_dump for backups. Your existing PostgreSQL backup scripts work on FerretDB's data. No new tools, no new processes:
pg_dump -h localhost -U postgres ferretdb_db > backup.sqlAccess the extension ecosystem. PostGIS for geospatial queries. pgvector for embeddings. pg_cron for scheduled jobs. TimescaleDB for time-series. These extensions work alongside your FerretDB data because it's all PostgreSQL under the hood.
Consolidate infrastructure. If you're already running PostgreSQL for your relational data, FerretDB means you don't need a second database system. One backup strategy, one monitoring setup, one set of operational playbooks.
When to Stay on MongoDB
Be honest with yourself about your requirements. FerretDB is not ready for every workload.
Stay on MongoDB if:
- You rely on change streams for real-time features (live dashboards, event-driven architectures)
- You use
$lookupheavily across many collections and refactoring would be impractical - You depend on Atlas Search for full-text or vector search
- You use time-series collections for IoT or metrics data
- Write throughput is critical and you need WiredTiger's purpose-built performance
- Your team is invested in the Atlas ecosystem (Charts, Realm, App Services)
FerretDB is actively developing. Features like $lookup and change streams may arrive in future releases. But "may arrive later" is not a migration plan. Base your decision on what works today.
Wrapping Up
Migrating from MongoDB to FerretDB is straightforward if your application uses standard CRUD operations and basic aggregation. The process is: check your codebase for unsupported features, set up FerretDB, dump and restore your data, swap the connection string, run your tests.
The compatibility gaps are real, and you should respect them. But for applications that don't use $lookup, change streams, or text search, the migration removes the SSPL licensing concern and consolidates your data onto PostgreSQL infrastructure.
Manage your local instances:
spindb stop ferret-migration # Stop FerretDB
spindb start ferret-migration # Start FerretDB
spindb connect ferret-migration # Connect with mongosh
spindb list # See all your database instancesSpinDB supports 20+ engines, so you can test both MongoDB and FerretDB side by side before committing to the migration. For a hosted option, Layerbase Cloud provisions either engine in seconds:
Or install Layerbase Desktop for a visual interface to manage your local databases.