Skip to content

Migrating from MongoDB to FerretDB

10 min readMongoDBFerretDBDatabases

Short version: FerretDB implements the MongoDB wire protocol on top of PostgreSQL, so your driver and your mongodb:// connection string stay as they are, and the data moves with mongodump and mongorestore, or in one pass through the Atlas migration page if you are landing on Layerbase Cloud. The copy is the mechanical part. The real work is the compatibility check, because $lookup, $graphLookup, change streams, $text search, capped collections, $merge and $out, $jsonSchema validation, and time-series collections are not supported, so grep for them before you touch any infrastructure. If none of them appear in your code, the migration is a connection-string change and a test run; if any of them are load-bearing, the right answer is to stay on MongoDB. Worth knowing before you plan around it: Layerbase does not host MongoDB itself, for licensing reasons, so FerretDB is the document engine in the cloud and the MongoDB card in the create flow provisions one.

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

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, insertMany
  • find, findOne with projections and sorting
  • updateOne, updateMany with $set, $unset, $inc, $push, $pull, $addToSet
  • deleteOne, deleteMany
  • replaceOne

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, estimatedDocumentCount
  • distinct

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:

FeatureMongoDBFerretDBWorkaround
$lookupCross-collection joins in aggregationNot supportedTwo queries joined in application code, or query PostgreSQL directly
$graphLookupRecursive graph traversalNot supportedApplication-level recursion, or PostgreSQL recursive CTEs
Change streamsReal-time document change notificationsNot supportedPostgreSQL LISTEN/NOTIFY
$text searchBuilt-in text indexes and searchNot supportedPostgreSQL tsvector/tsquery, or a search engine like Meilisearch
Capped collectionsFixed-size, insertion-order collectionsNot supportedApplication-level cleanup, or PostgreSQL partitioning with retention policies
$merge / $outWrite aggregation results to a collectionNot supportedFetch results in app code and insert into target collection
Schema validation$jsonSchema on createCollectionNot supportedApplication-level validation, or PostgreSQL constraints on the underlying table
Time-series collectionsOptimized time-series storageNot supportedPostgreSQL with TimescaleDB extension, or a purpose-built time-series DB like QuestDB or InfluxDB
Atlas SearchFull-text and vector search (Atlas only)Not supportedPostgreSQL 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.

bash
# 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:

  • $lookup in 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/NOTIFY after migrating.
  • $text for 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 the Layerbase CLI

The fastest way to test the migration locally is the Layerbase CLI (formerly SpinDB). One CLI, no Docker, no manual configuration. (What is the Layerbase CLI?)

Install the Layerbase CLI:

bash
npm i -g layerbase    # npm
pnpm add -g layerbase # pnpm

Create a FerretDB instance:

bash
lbase create ferret-migration -e ferretdb --start

The Layerbase CLI downloads FerretDB, provisions the PostgreSQL backend, and starts the server. Get the connection URL:

bash
lbase url ferret-migration
text
mongodb://127.0.0.1:27018

That's a standard mongodb:// URL. Your existing MongoDB driver connects to it without any code changes.

Migrate Your Data

If you're moving to Layerbase Cloud, you can skip the manual steps below: paste your MongoDB connection string on the MongoDB Atlas migration page and Layerbase copies your collections, documents, and indexes into a managed FerretDB database, verifies the document counts against your source, and reports anything it couldn't bring across. It works with Atlas and self-hosted MongoDB alike.

For a local or self-managed FerretDB, 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

bash
mongodump --uri="mongodb://localhost:27017" --db=myapp --out=./dump

This creates a ./dump/myapp/ directory with BSON files for each collection.

If your MongoDB instance requires authentication:

bash
mongodump --uri="mongodb://user:pass@your-mongo-host:27017/myapp?authSource=admin" --out=./dump

Step 2: Restore into FerretDB

bash
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:

bash
mongosh "mongodb://localhost:27018/myapp"
javascript
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.

typescript
// Before
const MONGO_URL = 'mongodb://localhost:27017/myapp'

// After
const MONGO_URL = 'mongodb://localhost:27018/myapp'

Now run your test suite:

bash
pnpm test

If 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:

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:

bash
pg_dump -h localhost -U postgres ferretdb_db > backup.sql

Access 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 $lookup heavily 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.

FAQ

How do I find out whether my app will actually run on FerretDB?

Grep first, then run your tests. The grep list in Check Your Codebase takes five minutes and tells you whether any unsupported feature is in the codebase at all. After that, point a local FerretDB instance at your test suite: a failing test names the exact operation FerretDB does not implement, which is a far better inventory than reading a compatibility table and guessing. The data transfer is mechanical, so this is the step that decides whether the migration happens.

What comes across in the copy, and what gets skipped?

Collections, documents, and ordinary indexes come across as they are. An index FerretDB cannot create is skipped in full rather than created without its options, and it is named in the migration report so you can decide whether to rebuild it by hand. Views and time-series collections are not copied and are listed the same way. Document counts are verified against the source when the copy finishes, and there is a ceiling: a collection over five million documents is refused, so a bigger move needs a conversation with support first.

Does my MongoDB need to be offline while it copies?

It keeps serving, but the read is against your live database rather than a point-in-time snapshot, so writes landing partway through can arrive unevenly across collections. Copy from a quiet database, or pause writes for the duration. The cutover afterward is a connection string, since your driver does not know the backend changed.

What do I use instead of change streams and $text once I am on FerretDB?

The replacements are all on the PostgreSQL side, which is the compensation for the gaps. Change streams become LISTEN and NOTIFY, or polling if the use case is cache invalidation. $text becomes PostgreSQL full-text search over the underlying JSONB, or a dedicated engine like Meilisearch if search is a real feature rather than a lookup box. $lookup becomes two queries joined in application code, or a SQL join against the tables directly.

Why not just run MongoDB on Layerbase and skip all of this?

MongoDB's license does not permit it, which is the whole reason FerretDB is here. The MongoDB card in the create flow provisions FerretDB on the same wire protocol, so the connection string and driver you would have used still apply. If the compatibility gaps rule FerretDB out for your workload, the correct move is to stay where you are rather than force the migration.

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:

bash
lbase stop ferret-migration    # Stop FerretDB
lbase start ferret-migration   # Start FerretDB
lbase connect ferret-migration # Connect with mongosh
lbase list                     # See all your database instances

The Layerbase CLI supports 20+ engines, so you can test both MongoDB and FerretDB side by side locally before committing to the migration. For a hosted option, Layerbase Cloud provisions FerretDB in seconds (MongoDB itself is not creatable in the cloud for licensing reasons; the MongoDB card in the create flow provisions FerretDB on the same wire protocol):

Or install Layerbase Desktop for a visual interface to manage your local databases.