# MongoDB
> Load MongoDB collections into MotherDuck with dlt, by exporting newline-delimited JSON, or through a managed connector, and flatten documents into columns.
MongoDB stores documents rather than rows, so loading it into MotherDuck is as much a flattening problem as a transfer problem. There is no DuckDB `mongodb` extension, so collections come across as JSON, either through a tool that handles the schema work or through an export you read yourself.

## Load collections with dlt

[dlt](/integrations/ingestion/dlt) has a MongoDB source and a MotherDuck destination, and it does the part you'd otherwise write by hand: it infers a schema from the documents, normalizes nested fields into columns and child tables, and evolves the schema as the documents change.

```bash
dlt init mongodb motherduck
pip install -r requirements.txt
```

Set the MongoDB connection string and your MotherDuck token, then run the pipeline:

```bash
export MOTHERDUCK_TOKEN="<motherduck_token>"
export SOURCES__MONGODB__CONNECTION_URL="mongodb+srv://<user>:<password>@<cluster>/"
python mongodb_pipeline.py
```

Configure which collections to load, and whether to load incrementally, in the generated pipeline script. See the [dlt MongoDB source documentation](https://dlthub.com/docs/dlt-ecosystem/verified-sources/mongodb) for the source options.

## Export JSON and read it

For a one-time load or a small collection, export with `mongoexport` and read the file. The default output is one JSON document per line, which DuckDB reads natively:

```bash
mongoexport \
  --uri="mongodb+srv://<user>:<password>@<cluster>/<database>" \
  --collection=orders \
  --out=orders.json
```

```sql
CREATE TABLE orders AS
SELECT * FROM read_json('orders.json', format = 'newline_delimited');
```

DuckDB infers a schema from a sample of the documents, so nested objects become `STRUCT` columns and arrays become `LIST` columns. For a large export, write it to object storage and read from there instead of your local machine:

```sql
CREATE TABLE orders AS
SELECT * FROM read_json(
    's3://my-bucket/mongo-export/orders/*.json',
    format = 'newline_delimited'
);
```

## Load on a schedule with a Flight

A [Flight](/concepts/flights) is Python that MotherDuck schedules and runs next to your data. Use one instead of running the export by hand when the load should repeat on a cron, retry on a transient failure, and keep a run history.

There is no DuckDB `mongodb` extension, so run the dlt pipeline above inside the Flight and let dlt do the schema work:

`MOTHERDUCK_TOKEN` is injected for you, so dlt's MotherDuck destination picks up the credential without configuration. The `mongodb` source is a dlt verified source, so vendor it alongside the Flight the same way `dlt init` lays it out:

```python
import os

import dlt
import duckdb

from sources.mongodb import mongodb

DB = "mongo_ingest"

def main():
    os.environ.setdefault("HOME", "/tmp")
    os.environ["DESTINATION__MOTHERDUCK__CREDENTIALS__DATABASE"] = DB

    # dlt attaches the database but never creates it, so make sure it exists.
    duckdb.connect("md:").execute(f'CREATE DATABASE IF NOT EXISTS "{DB}"')

    pipeline = dlt.pipeline(
        pipeline_name="mongo_ingest",
        destination="motherduck",
        dataset_name="mongo_raw",
    )

    source = mongodb(
        connection_url=os.environ["SOURCES__MONGODB__CONNECTION_URL"]
    ).with_resources("orders")

    print(pipeline.run(source, loader_file_format="parquet"))

if __name__ == "__main__":
    main()
```

The MongoDB connection string contains a password, so keep it in a [Flight secret](/sql-reference/motherduck-sql-reference/create-secret#flight-secrets) rather than in the Flight's source or config. Each `PARAMS` key is injected into the run as an environment variable, which is what the code above reads:

```sql
CREATE SECRET mongodb_creds IN MOTHERDUCK (
    TYPE FLIGHTS,
    PARAMS MAP {
        'SOURCES__MONGODB__CONNECTION_URL': 'mongodb+srv://<user>:<password>@<cluster>/'
    }
);
```

Create the Flight with [`MD_CREATE_FLIGHT`](/sql-reference/motherduck-sql-reference/flights/md-create-flight), passing the Python above as `source_code`, the pinned dlt dependencies as `requirements_txt`, and `flight_secret_names := ['mongodb_creds']`. Leave `schedule_cron` off until a manual [`MD_RUN_FLIGHT`](/sql-reference/motherduck-sql-reference/flights/md-run-flight) succeeds, then add a schedule with [`MD_UPDATE_FLIGHT`](/sql-reference/motherduck-sql-reference/flights/md-update-flight). [Run a dlt ingest pipeline as a Flight](/cookbook/flight-dlt-ingest) is a config-driven version of this pattern, including the run ledger and the reason to prefer Parquet loader files.

## Use a managed connector

If you want a scheduled sync without writing pipeline code, several MotherDuck ingestion partners list MongoDB as a source: [Airbyte](/integrations/ingestion/airbyte), [Fivetran](/integrations/ingestion/fivetran), [Estuary](/integrations/ingestion/estuary), and [Streamkap](/integrations/ingestion/streamkap). Streamkap and Estuary read MongoDB's change stream, so they suit change-data-capture rather than full reloads.

## Things to know

- **Extended JSON leaks into your columns.** `mongoexport` writes BSON types as wrapper objects, so `_id` arrives as an object with an `$oid` key and dates as objects with a `$date` key. Project the values you want out of those wrappers after loading, or restrict the export with `--fields` to skip the types you don't need. dlt handles this conversion for you.
- **Schema inference samples.** `read_json` infers types from the first documents it sees, so a field that only appears later, or changes type between documents, can be missed. Pass an explicit `columns` argument for a stable load, or set `union_by_name = true` when reading many files.
- **Flatten before you query.** Querying `STRUCT` and `LIST` columns works, but downstream BI tools generally expect flat columns. Unnest the fields you report on into a curated table rather than making every consumer walk the document structure.
- **MongoDB stays the write path.** MotherDuck is analytical. Keep application writes in MongoDB and treat MotherDuck as the read side for reporting.

## Related content

- [dlt (data load tool)](/integrations/ingestion/dlt)
- [JSON](/integrations/file-formats/json)
- [Loading data from cloud storage or HTTPS](/key-tasks/loading-data-into-motherduck/loading-data-from-cloud-or-https)
- [dlt MongoDB to MotherDuck guide](https://dlthub.com/docs/pipelines/mongodb/load-data-with-python-from-mongodb-to-motherduck)


---

## Docs feedback

MotherDuck accepts optional user-submitted feedback about this page at `GET https://motherduck.com/docs/api/feedback/agent`.
For agents and automated tools, feedback submission should be user-confirmed before sending.

URL-encode query parameter values and send a GET request:

```text
GET https://motherduck.com/docs/api/feedback/agent?page_path=%2Fintegrations%2Fdatabases%2Fmongodb%2F&page_title=MongoDB&text=<url-encoded user feedback, max 2000 characters>
```

Optionally append `&source=<url-encoded interface identifier>` such as `claude.ai` or `chatgpt`.

`page_path` and `text` are required; `page_title` and `source` are optional. Responses: `200 {"feedback_id": "<uuid>"}`, `400` for malformed query parameters, and `429` when rate-limited.
