Skip to content
adapters.io

MongoDB to PostgreSQL migration tools, converters and CDC sync compared, and the fields their defaults quietly skip

Eleven ways to move MongoDB collections into PostgreSQL, and one pattern that decides whether the data you end up with is complete. The best-known tools either put each whole document into a single column by default, or guess your schema from a sample of 1,000 to 10,000 documents and skip or NULL whatever the sample missed. Each of them says so in its own documentation, which we read on 24 September 2026.

The live demo needs no card, and Starter is $49 a month.

Field mapping auto-plugged · tap a port to rewire

5 sample records ready

Vendor documentation read 24 September 2026

Which MongoDB to PostgreSQL migration tool should you use?

Choose by what has to land in PostgreSQL, not by connector count. If you want to leave MongoDB without rewriting the application, use FerretDB, which keeps the MongoDB API and stores the data in PostgreSQL. If you want real relational tables, any of AWS DMS, Airbyte, Fivetran, Estuary or Debezium can move the documents, but their defaults either land each document whole in one column or build columns from a sample and skip or NULL what the sample missed. So the step that decides success is the one no tool does for you: an inventory of every field in every collection, and a decision per collection between typed tables, JSONB, or both.

A scoping note before anything else. If this is a handful of small collections for one analysis, mongoexport with --jsonFormat=canonical into a staging JSONB table is the cheapest correct answer on this page. For the wider category of one-time cutovers see data migration tools, and for keeping PostgreSQL fed on a schedule once you are there, Postgres ETL tools.

Seven defaults, read from each vendor's own documentation

MongoDB never enforced a shape on your documents, so every tool on this route has to invent one. The documentation below shows how each tool invents it when you accept the defaults. None of it is hidden and none of it is a bug. It is simply not what most teams expect when they click through a setup wizard, and the consequences only show up after the load. The last column is what we would ship.

Tool and setting What its documentation says Why it matters to a buyer What we would ship
AWS DMS, default mode DMS documents that in document mode "the document data is consolidated into a single column named _doc", and that "Document mode is the default setting". Accept the defaults and the migration completes with every document in one column. The row counts match and the project has not started, because nothing in PostgreSQL has a type yet. Most teams discover this after the full load, not before. Decide the landing shape before the first run
AWS DMS, table mode Table mode "transforms each top-level field in a MongoDB document into a column". The number of documents scanned to build the columns defaults to 1,000, and "If there are fields that don't exist in the target, those fields aren't replicated." A collection of ten million documents gets its schema from the first thousand. A field introduced by last spring's release, or present only on enterprise accounts, never becomes a column and is never copied. DMS also requires one data type per field across the collection in this mode. Inventory every field with an aggregation, not a sample
Airbyte, default mode Airbyte states the MongoDB v2 source "enforces a schema" by default, sampling a configurable number of documents, "Default is 10,000". It warns that "no sample size can guarantee a complete or stable schema", and that records with structural mismatches "would be written as NULL". Ten times the DMS sample and the same shape of risk. The failure is quieter here, because a mismatched value becomes NULL rather than an error, so a revenue column can be partly empty with every sync green. Count NULLs per column against the source after the first load
Airbyte, schemaless mode In schemaless mode each record contains only "_id" and "data", and Airbyte notes that "no field will be omitted and no document will be rejected." Nothing is lost and nothing is typed. This is the honest choice for fast-changing collections, but it is the same payload landing as DMS document mode, and the view layer that makes it usable is yours to write and maintain. Use it as a landing zone, never as the table analysts query
Fivetran, default mode Fivetran documents packed mode as "the default mode": it writes data "without unpacking nested fields", so the data column holds the whole document as JSON. Unpacked mode exists, and "We only unpack one layer of nested fields and infer types." Even with unpacking switched on, a customer address inside a billing object inside an order stays JSON. Documents in real applications are usually three or four levels deep, so a one-layer unpack gets you part of the way and bills you for all of it. Map the deep paths you need explicitly
mongoexport, default format mongoexport writes Extended JSON and --jsonFormat defaults to relaxed. MongoDB says relaxed mode "emphasizes readability and interoperability at the expense of type preservation", and that conversion from it "can lose type information". In relaxed output an Int64 is a plain integer and a date is an ISO string, so the loader sees a number and a string rather than a bigint and a timestamp. Decimal128 keeps its $numberDecimal wrapper in both modes. Nobody gets an error message in either case. Always pass --jsonFormat=canonical
Debezium, flattening Debezium's MongoDB events are hierarchical, and sinks that cannot read them need the ExtractNewDocumentState transform. Its array.encoding option defaults to array, which needs every element to be of one type. The alternative, document, turns elements into fields named _0, _1 and so on. The JDBC sink that writes to PostgreSQL wants flat rows, so the flattening transform is not optional. An array of mixed values forces a choice between failing on the default and columns called _0 and _1 that no analyst will understand. Model arrays as child tables before choosing an encoding

Read the rows together and one pattern stands out. The tools split into those that land a payload and leave modeling to you, and those that model from a sample and drop what the sample missed. Neither is wrong, and both are cheaper to fix before the first load than after it. The same question, what actually lands, decides the warehouse version of this route too, where Snowflake's own connector creates a two-column table; that is compared on MongoDB to Snowflake migration tools. For how change streams compare with log-based capture on relational sources, see change data capture tools.

BSON types in PostgreSQL, and the wrong target a default picks

A MongoDB to PostgreSQL converter is mostly a type converter, and the types come through Extended JSON on the way. The second column shows how mongoexport writes each type. The last column is the mistake we see most often, and none of these produce an error at load time except the NUL character, which PostgreSQL rejects outright.

BSON type In Extended JSON Right PostgreSQL target Common wrong target
ObjectId {"$oid": "..."} in both modes text holding the 24 hex characters, or bytea for the 12 raw bytes Leaving the $oid wrapper inside JSONB, so joins compare objects
Date Relaxed: ISO string. Canonical: $date with $numberLong millis timestamptz, since BSON dates are UTC milliseconds text, or timestamp without time zone
Int64 (long) Relaxed: a plain integer. Canonical: $numberLong string bigint A JSON parser reading it as a double
Decimal128 {"$numberDecimal": "..."} in both modes numeric, with the scale you actually use double precision, which rounds currency
Double Relaxed: a plain number. Canonical: $numberDouble double precision numeric, which looks exact but is not
String containing \u0000 An escaped string text with the NUL stripped, or bytea jsonb or text, where the load fails
Array of subdocuments A JSON array A child table keyed on parent id plus position Columns on the parent row, or DMS CLOB
Embedded document A JSON object Typed columns for used paths, jsonb for the rest Flattened names past 63 bytes, truncated
Field with mixed types Number in old documents, string in new jsonb plus a view that casts deliberately A typed column that turns mismatches into NULL
Missing field vs null Absent key vs an explicit null A nullable column, with jsonb kept to tell them apart Collapsing both to NULL when they mean different things

Two PostgreSQL rules catch teams out on names rather than values. PostgreSQL keeps at most 63 bytes of an identifier and truncates the rest, and it folds unquoted names to lower case, so a MongoDB field called createdAt becomes createdat unless you quote it forever. Rename to snake_case in the mapping. The same type-by-type approach for relational sources is on MySQL to PostgreSQL migration tools and SQL Server to PostgreSQL migration tools, and the one-line checks that prove each mapping is safe before you load are in our guide to converting MongoDB documents to PostgreSQL tables and JSONB.

The six mechanisms buyers keep confusing

"MongoDB to PostgreSQL" covers six products with different owners, different bills and different results. One of them does not convert your data model at all, and for some teams that is exactly the point.

Mechanism Owner How it moves data Who it is for What to watch
Dump and load You, on mongoexport plus COPY or a script Exports collections to Extended JSON, loads into a staging JSONB column, then reshapes with SQL. Small collections and a cutover window measured in hours, not minutes. Point in time only. Relaxed format by default.
Managed migration AWS DMS Full load plus change capture from the oplog, in document mode or table mode. Teams already in AWS doing a one-time move with a planned cutover. Payload by default. Table mode schema from 1,000 documents.
ELT pipeline Fivetran, Airbyte, Estuary, Hevo Reads the change stream and writes rows into PostgreSQL on a schedule or continuously. Teams keeping MongoDB live and copying data into Postgres for reporting. Sampled schemas and one-layer unpacking. Metered bills.
Open source CDC You, on Debezium plus a JDBC sink Streams change events through Kafka Connect and writes flat rows after a flattening transform. Teams already running Kafka who want full control of the mapping. No license fee, real operational load.
Wire protocol swap FerretDB on the DocumentDB extension Keeps the MongoDB API and stores the documents in PostgreSQL, so drivers keep working. Teams leaving MongoDB hosting or licensing without rewriting the app. You gain PostgreSQL operations, not relational tables.
Mapped sync (Adapters) Adapters You declare document paths once, we land them as typed columns and arrays as child tables, then sync incrementally. Teams who want typed Postgres tables on a flat bill. Not a sub-second streaming platform.

MongoDB to PostgreSQL migration tools and converters compared

Billing units rather than price tags, except where the vendor publishes the number itself: Fivetran's $5 base charge per connection and Estuary's $0.50 per GB were both read from their own pricing pages on 24 September 2026. Everything else is quoted, metered in ways that depend on your data, or free. Where a tool is wrong for a job, the last column says so, including for us.

Tool Approach Best for Billing unit What to watch
AWS DMS Full load plus CDC, document mode or table mode One-time moves inside AWS with a cutover date Replication instance hours or serverless capacity Default lands _doc; table mode samples 1,000 documents
Airbyte Change stream source, schema enforced or schemaless Teams who want open source they can patch Free self-hosted, credits on cloud Mismatched values written as NULL by default
Fivetran Managed connector, packed or one-layer unpacked Teams who want zero pipeline operations Monthly active rows, plus $5 base per connection Packed JSON by default; deep paths stay JSON
Estuary Streaming capture, change streams or batch Low latency, including servers without change streams $0.50 per GB moved plus connector instances Batch mode on standalone servers is not CDC
Hevo Managed pipeline with a mapping UI Small teams with no data engineer Events per month Document rewrites count as events
Debezium plus JDBC sink Change stream to Kafka to PostgreSQL Organizations already standardized on Kafka Infrastructure and engineering time Needs ExtractNewDocumentState; arrays need a decision
FerretDB MongoDB wire protocol on PostgreSQL Keeping the app unchanged while leaving MongoDB Open source, Apache 2.0 Documents stay documents; no relational model
mongoexport plus COPY Extended JSON dump into staging JSONB Small collections and proofs of concept Free, plus your time Relaxed default loses types; no incremental path
Custom script Driver reads, typed inserts, your own mapping One-off moves with unusual reshaping Engineering time Becomes a pipeline nobody owns
Relational Migrator MongoDB's own tool, relational into MongoDB The opposite direction of this page Free It does not migrate MongoDB to PostgreSQL
Adapters Declared path to column mapping, incremental sync, per-record logs Typed Postgres tables without a metered bill Flat monthly from $49, not metered by rows Not a sub-second streaming platform

One row deserves a second look. MongoDB Relational Migrator is the tool many teams find first, because it comes from MongoDB and has "relational" and "migrator" in its name. MongoDB describes it as "a free tool to help you migrate data from a relational database to MongoDB", with Oracle, SQL Server, MySQL and PostgreSQL as sources. It does not run in the direction this page covers. For what a cutover of this size costs once the tooling is chosen, see what a data migration really costs.

Six numbers worth knowing before you sign anything

_doc

Where AWS DMS puts each whole document by default. Document mode is the default setting, so typed columns are something you opt into.

AWS DMS MongoDB source docs, read 24 Sep 2026

1,000

Documents DMS scans by default to decide the columns in table mode. Fields missing from the target are not replicated.

AWS DMS MongoDB source docs, read 24 Sep 2026

10,000

Airbyte's default sample size for inferring a MongoDB schema. Values that do not fit the inferred structure are written as NULL.

Airbyte MongoDB v2 docs, read 24 Sep 2026

1 layer

How deep Fivetran unpacks nested fields when unpacking is on. Packed mode, with the whole document as JSON, is the default.

Fivetran MongoDB docs, read 24 Sep 2026

63 bytes

The longest identifier PostgreSQL keeps. Longer names are truncated, and flattened document paths get long quickly.

PostgreSQL lexical structure docs, read 24 Sep 2026

\u0000

The character jsonb rejects outright, because PostgreSQL text cannot represent it. MongoDB strings can contain it.

PostgreSQL JSON types docs, read 24 Sep 2026

Eight ways this migration goes wrong while every check stays green

Moving from a database that enforced no shape to one that enforces every shape should produce errors. Mostly it does not, because the tools are built to keep going. These are the eight we would check on any MongoDB to PostgreSQL migration before believing a single number it produces.

The failure What you see What is actually happening
The field the sample never saw Full load succeeded, counts match Schema inference read the first 1,000 or 10,000 documents. A field that only appears later, on newer records or one customer segment, never became a column and was never copied.
The value that became NULL Every sync green A field that is a number in most documents and a string in some lands the misfits as NULL. The column looks populated, the totals are low, and no log line says why.
The payload nobody modeled Migration marked complete Document mode or packed mode put every document into one column. The data is all there and the application still cannot run a single query it used to run.
The types relaxed JSON dropped Export and load both clean Relaxed Extended JSON wrote 64-bit integers as plain numbers and dates as strings. They load as numeric and text, and the loss only surfaces when a join or a date range misbehaves.
The duplicate key jsonb collapsed Load succeeded PostgreSQL jsonb keeps only the last value when a key repeats in the input. Any document that carried a repeated key now has one value and no trace of the other.
The second layer still in JSON Unpacking switched on A one-layer unpack turns top-level fields into columns and leaves everything below as JSON. Reports built on the columns look complete and silently ignore the nested detail.
Arrays multiplying rows Every job green, totals inflated Line items flattened onto the order row, or joined without care, turn one order into many rows. Revenue is overstated while the pipeline behaves exactly as configured.
The oplog gap after an outage Sync resumes and carries on Change streams resume from a token in the oplog. If the pipeline was down longer than the oplog window, that position is gone, and the missing changes stay missing unless you reconcile.

Six of the eight share a cause: a shape was guessed rather than declared. The guard is the same for all of them, a field inventory before the load and a reconciliation of counts and NULLs after it. The relational equivalent of this list, where the source did enforce a schema and the risks are type conversions instead, is on Oracle to PostgreSQL migration tools.

Six steps that decide whether this migration works

Step 1

Inventory every field, not a sample

Run an aggregation over each full collection that lists every field name, how often it appears and which BSON types it holds. It takes minutes and it replaces the 1,000 or 10,000 document guess that DMS and Airbyte make by default. Everything else on this page depends on this list.

Step 2

Choose a landing shape per collection

Typed tables for stable, heavily queried collections. A JSONB column for fast-changing ones. Usually both: the paths you filter and join on as columns, the rest of the document kept whole beside them. Write the choice down, because it drives the tool choice, not the other way round.

Step 3

Give arrays their own tables

An order with an array of line items becomes an orders table and an order_items table keyed on order id plus position. Decide this before the first load. Retrofitting child tables after reports exist on a flattened shape is the most expensive change on this route.

Step 4

Fix names and types before PostgreSQL sees them

Map camelCase names to snake_case so nobody has to quote identifiers forever, keep flattened names under 63 bytes, strip NUL characters from strings, and map Decimal128 to numeric and dates to timestamptz explicitly. Do it in the mapping, not in a cleanup script afterwards.

Step 5

Full load, then change capture to cutover

Load history once, then follow the change stream until the cutover date so the switch takes minutes rather than a weekend. This needs a replica set or sharded cluster. If you run a standalone mongod, converting it to a single-node replica set is the first ticket, not the last.

Step 6

Reconcile counts and NULLs before cutover

Compare document counts per collection with row counts, and the number of documents holding each field with the non-NULL count in its column. This is the only check that catches sampled schemas and mismatches written as NULL, because neither produces an error.

Why US teams fund this project

Leaving MongoDB Atlas for Postgres

A US SaaS company consolidating on one database engine to cut hosting cost and operational surface, with a hard date tied to a renewal.

Reporting that MongoDB struggles with

Finance and ops teams need joins across customers, orders and payments. Moving those collections into typed PostgreSQL tables lets them use SQL and BI tools directly.

An acquisition on a different stack

The acquired product runs on MongoDB and the parent company runs on PostgreSQL. The documents have to fit an existing relational model, not a new one.

Compliance and audit requirements

Auditors want constraints, foreign keys and a schema they can read. A typed PostgreSQL model makes controls on financial records far easier to evidence.

Keeping MongoDB, adding Postgres

The application stays on MongoDB, while a PostgreSQL copy updated by change capture serves reporting, search or a second service that needs relational data.

Retiring self-hosted MongoDB

A single self-hosted node nobody wants to patch. The move either goes to PostgreSQL tables or, to avoid an app rewrite, to FerretDB on PostgreSQL.

Five jobs where you should not pick us

A comparison page that never says the competition wins is an advert. These are the cases where something else on this page is the right answer.

  • You want to leave MongoDB without touching application code. FerretDB on PostgreSQL keeps the MongoDB API and your drivers, and it is the better answer for that goal than any migration tool, including ours.
  • This is a one-time move entirely inside AWS with a fixed cutover. DMS is already in your account and bills by the hour. Configure table mode carefully, reconcile after, and you do not need a subscription.
  • You already run Kafka and Debezium. A working change stream pipeline plus a JDBC sink covers this route, and adding a vendor buys you little.
  • You need sub-second latency between a MongoDB write and a PostgreSQL row. Streaming platforms built for that will beat our scheduled sync.
  • The whole job is a few small collections for one analysis. mongoexport with --jsonFormat=canonical, a staging JSONB table and an afternoon of SQL will do it for nothing.

Four questions to ask any vendor on this list

Question 01

How do you decide which fields become columns?

If the answer is a sample of the first thousand or ten thousand documents, ask what happens to a field that appears later. Two of the best-known tools on this page skip such fields or write NULL by default, and both say so in their own documentation.

Question 02

What happens to arrays of subdocuments?

Ask for the destination DDL for one real collection. The answers range from a child table, to columns named _0 and _1, to a CLOB, to leaving the array as JSON. Only one of those is what a reporting team can use without rework.

Question 03

What happens when a value does not match the column type?

An error you can see, a NULL you cannot, or the value kept in a JSON column beside the typed one. All three exist in the tools above. Know which one you are buying before the first load, not after the first wrong report.

Question 04

What do you need from our MongoDB deployment?

Change capture needs a replica set or sharded cluster, and some tools need broad read roles. A standalone server rules out most CDC tools or drops them to batch mode. Find this out in week one, because it can add infrastructure work before anything moves.

Questions buyers ask about MongoDB to PostgreSQL migration

How do I migrate MongoDB to PostgreSQL?
Inventory every field in every collection first, decide per collection whether documents become typed tables, a JSONB column, or both, then run a full load followed by change capture until cutover. The tool matters less than that first inventory, because the popular tools infer your schema from a sample and quietly skip fields they never saw.
What is the best MongoDB to Postgres migration tool?
For a one-time move inside AWS, DMS is hard to beat on cost. For an ongoing sync with typed columns, a managed pipeline or a mapped sync fits better. If you want to keep the MongoDB API and simply run it on PostgreSQL, FerretDB is a different and often better answer. Pick by what must land, not by connector count.
Is there a MongoDB to PostgreSQL converter?
Several, but they convert different things. Data converters such as DMS, Airbyte and Fivetran move documents. Query converters rewrite MongoDB queries as SQL. Neither converts your aggregation pipelines automatically. And MongoDB Relational Migrator, the tool people often find first, migrates in the opposite direction, from relational databases into MongoDB.
Should MongoDB documents go into JSONB or tables in PostgreSQL?
Both, usually. Put the fields you filter, join and aggregate on into typed columns, keep the rest of the document in a JSONB column, and give arrays of subdocuments their own child tables. A pure JSONB landing is fast to build and slow to query well. A pure table landing breaks on the first document that changes shape.
Can Airbyte move MongoDB to PostgreSQL?
Yes. Airbyte reads MongoDB through change streams and writes to PostgreSQL. By default it enforces a schema inferred from a sample of 10,000 documents, and its documentation states that records with structural mismatches are written as NULL. Schemaless mode avoids that by landing only _id and data, which moves the modeling work to you.
How does CDC from MongoDB to PostgreSQL work?
Through MongoDB change streams. A connector tails the ordered feed of inserts, updates and deletes and applies each one to PostgreSQL as an upsert or delete. Change streams exist only on replica sets and sharded clusters, so a standalone mongod has no change feed at all and must be converted before any CDC tool will work.
Can AWS DMS migrate MongoDB to PostgreSQL?
Yes, MongoDB is a supported DMS source and PostgreSQL a supported target. The detail to know is that document mode is the default, which puts each whole document into a single _doc column. Table mode flattens top-level fields, but it infers columns from the first 1,000 documents, and fields missing from the target are not replicated.
How do I export data from MongoDB to PostgreSQL?
mongoexport writes Extended JSON, which PostgreSQL can load into a JSONB column and then reshape with SQL. Pass --jsonFormat=canonical. The default relaxed format, in MongoDB's own words, can lose type information, so 64-bit integers and dates arrive as plain numbers and strings. It is a point-in-time copy with no change tracking.
Can PostgreSQL replace MongoDB without rewriting the application?
Sometimes. FerretDB is an open source proxy that speaks the MongoDB wire protocol and stores data in PostgreSQL through the DocumentDB extension, so drivers keep working. That avoids a schema redesign, but it also means you have not gained relational tables. It suits teams leaving MongoDB licensing or hosting, not teams who want SQL.
How much does a MongoDB to PostgreSQL migration cost?
The data movement is rarely the big line. Tools range from free (mongoexport, Debezium, FerretDB) to metered: Fivetran bills on monthly active rows plus a $5 base per connection, Estuary on data moved plus connector instances. The real cost is the schema design and rewriting queries, which no tool on this page does for you.

For the wider vendor landscape see the best data integration tools, and for the mapping itself, field by field, read how to convert MongoDB documents to PostgreSQL tables and JSONB.

Land MongoDB documents in PostgreSQL as typed tables

Declare the document paths that matter, send arrays to child tables, run the backfill, then let the same mapping sync incrementally with retries, alerts and per-record logs. From $49 a month, not metered by rows.

The live demo needs no card, and Starter is $49 a month.

Get started