
Importing and Exporting Data with mongoimport and mongoexport
Some data tasks don't deserve a script. The marketing team sends a CSV of 40,000 leads to load into a collection. A partner wants last month's orders as a spreadsheet. You need to copy a reference collection from staging into a fresh development cluster. Writing a custom loader for each of these wastes an afternoon, and it usually ends up slower and less robust than the tools MongoDB already ships.
mongoimport and mongoexport are command-line tools that move data between MongoDB and JSON, CSV, or TSV files. They handle batching, parallel insertion, type conversion, and Extended JSON so that you don't have to. They're also frequently misused: as a backup strategy (they aren't one), with CSV files that silently lose types, or with a --jsonArray flag that nobody remembers until the import fails.
This guide covers installing the tools, connecting to local and Atlas clusters, importing JSON and CSV (including typed columns and upserts), exporting with queries and field selection, preserving BSON types with Extended JSON, moving data between clusters, and the errors you're most likely to hit.
Installing the Tools
Both tools are part of the MongoDB Database Tools package, which is distributed separately from the server. Installing MongoDB Community on some platforms pulls it in automatically, but on others you'll need it on its own:
# macOS (Homebrew)
brew tap mongodb/brew
brew install mongodb-database-tools
# Debian/Ubuntu, after adding MongoDB's apt repository
sudo apt-get install -y mongodb-database-tools
# Verify
mongoimport --version
mongoexport --version
On Windows, download the MSI or ZIP from the MongoDB Database Tools download page and add its bin directory to your PATH. The official mongo Docker image also includes the tools, which is handy if you don't want them on your host:
docker run --rm -v "$PWD:/data" mongo:8 \
mongoimport --uri "mongodb://host.docker.internal:27017/shop" \
--collection products --file /data/products.json
The Database Tools are versioned independently of the server (with version numbers like 100.x), and recent releases support all current server versions.
Connecting to Your Cluster
Both tools accept a connection string with --uri, which is the simplest and most portable way to connect. Put the database name in the URI and the collection in --collection (or -c):
# Local server
mongoimport --uri "mongodb://localhost:27017/shop" -c products --file products.json
# Atlas: omit the password and you'll be prompted for it
mongoimport --uri "mongodb+srv://importer@cluster0.abcde.mongodb.net/shop" \
-c products --file products.json
Leaving the password out of the URI keeps it out of your shell history. For automation, read the URI from an environment variable instead of typing it inline. If you connect with a user defined in the admin database on a self-hosted server and get an authentication error, add ?authSource=admin to the URI.
For Atlas, your machine's IP must be on the project's IP access list, and the database user needs write permission on the target database (readWrite is enough) for imports, or read permission for exports.
Importing JSON
By default, mongoimport expects one JSON document per line, a format often called NDJSON or JSON Lines:
{"sku": "LMP-204", "name": "Brass Desk Lamp", "price": 89.99, "tags": ["lighting", "desk"]}
{"sku": "LMP-205", "name": "Walnut Floor Lamp", "price": 149.0, "tags": ["lighting", "floor"]}
{"sku": "BLB-011", "name": "Warm LED Bulb", "price": 4.25, "tags": ["bulbs"]}
mongoimport --uri "$MONGODB_URI" -c products --file products.ndjson
2026-09-23T08:41:07.412+0000 connected to: mongodb+srv://[**REDACTED**]@cluster0.abcde.mongodb.net/shop
2026-09-23T08:41:07.998+0000 3 document(s) imported successfully. 0 document(s) failed to import.
If your file is a single JSON array instead, which is what most APIs and JSON.stringify produce, add --jsonArray:
mongoimport --uri "$MONGODB_URI" -c products --file products.json --jsonArray
For large datasets, prefer the line-per-document format. It streams naturally, it's easy to split, append to, and inspect with command-line tools, and a single corrupt line doesn't invalidate the entire file.
Replacing or Merging Existing Data
By default, mongoimport inserts documents. Documents that collide with a unique index, including _id, fail individually and are reported, while the rest continue. You can change that behavior:
# Drop the collection first, then import (a clean reload)
mongoimport --uri "$MONGODB_URI" -c products --file products.ndjson --drop
# Upsert: replace existing documents that match on sku, insert new ones
mongoimport --uri "$MONGODB_URI" -c products --file products.ndjson \
--mode upsert --upsertFields sku
# Merge: update matching documents with the fields in the file, keep other fields
mongoimport --uri "$MONGODB_URI" -c products --file price-updates.ndjson \
--mode merge --upsertFields sku
# Delete: remove documents that match the file's records
mongoimport --uri "$MONGODB_URI" -c products --file discontinued.ndjson \
--mode delete --upsertFields sku
The difference between upsert and merge matters. upsert replaces the whole matched document, so fields not present in the file disappear. merge only sets the fields from the file, leaving the rest intact. For a price-update file that contains just sku and price, merge is almost certainly what you want.
Make sure the --upsertFields combination is backed by an index (ideally unique). Without one, each document in the file triggers a collection scan to find its match. If you omit --upsertFields, matching uses _id.
Importing CSV and TSV
CSV is the most common format for data from spreadsheets and other systems. Use --type csv (or --type tsv) and tell mongoimport where the field names come from:
sku,name,price,stock,dimensions.width,dimensions.height
LMP-204,Brass Desk Lamp,89.99,42,18,45
LMP-205,Walnut Floor Lamp,149.00,7,30,160
mongoimport --uri "$MONGODB_URI" -c products \
--type csv --headerline --file products.csv
With --headerline, the first row supplies field names. Dotted names like dimensions.width become nested documents:
{
_id: ObjectId("66f12d..."),
sku: "LMP-204",
name: "Brass Desk Lamp",
price: 89.99,
stock: 42,
dimensions: { width: 18, height: 45 }
}
If the file has no header row, list the fields yourself with --fields sku,name,price,stock or put them one per line in a file and pass --fieldFile.
Controlling Types with --columnsHaveTypes
Without type information, mongoimport guesses: things that look like numbers become numbers, everything else becomes a string. That guessing causes real problems. A ZIP code like 02134 loses its leading zero, a SKU like 1e5 turns into the number 100000, and dates stay as strings.
--columnsHaveTypes lets you declare each column's type in the header:
sku.string(),name.string(),price.decimal(),stock.int32(),zip.string(),addedAt.date(2006-01-02),active.boolean()
LMP-204,Brass Desk Lamp,89.99,42,02134,2026-09-01,true
LMP-205,Walnut Floor Lamp,149.00,7,10001,2026-09-12,false
mongoimport --uri "$MONGODB_URI" -c products \
--type csv --headerline --columnsHaveTypes --file products-typed.csv
Now price is stored as a Decimal128, zip keeps its leading zero, and addedAt is a real BSON Date. The supported types include auto(), string(), int32(), int64(), double(), decimal(), boolean(), binary(), and several date variants. The date() format uses Go's reference-time layout, where 2006-01-02 15:04:05 stands for "year-month-day hour:minute:second"; date_ms() and date_oracle() accept other formatting conventions.
If you can't edit the CSV header, the same typed field list works with --fields or --fieldFile. Storing money correctly is its own topic, covered in storing money with Decimal128.
Handling Messy Rows
Real CSV files have blank cells and bad values. Two flags help:
--ignoreBlanksskips empty cells instead of storing empty strings, so optional columns don't create"phone": ""everywhere.--parseGracecontrols what happens when a typed value can't be parsed:autoCast(the default) falls back to guessing,skipFielddrops the field,skipRowdrops the whole row, andstopaborts the import.
mongoimport --uri "$MONGODB_URI" -c leads \
--type csv --headerline --columnsHaveTypes \
--ignoreBlanks --parseGrace skipRow --file leads.csv
For imports where correctness matters more than completeness, --parseGrace stop combined with --stopOnError fails fast so you can fix the source file instead of discovering missing rows later.
Exporting Data
mongoexport writes a collection (or a filtered subset of it) to JSON or CSV. The basic form writes line-per-document JSON to standard output, or to a file with --out:
mongoexport --uri "$MONGODB_URI" -c products --out products.ndjson
Filtering, Sorting, and Selecting Fields
The --query flag takes a filter written in Extended JSON, which means operators like $gte work as usual but BSON types such as dates must use the Extended JSON form:
mongoexport --uri "$MONGODB_URI" -c orders \
--query '{"status": "shipped", "createdAt": {"$gte": {"$date": "2026-08-01T00:00:00Z"}, "$lt": {"$date": "2026-09-01T00:00:00Z"}}}' \
--fields orderId,customerEmail,total,createdAt \
--sort '{"createdAt": 1}' \
--out august-orders.ndjson
Wrap the query in single quotes so your shell doesn't interpret the $ characters. A plain string like "2026-08-01" would be compared as a string and match nothing, because createdAt holds real dates. --limit and --skip are also available, which is handy for grabbing a sample: --limit 100 gives you a quick fixture file for local development.
Exporting CSV for Spreadsheets
CSV exports require an explicit field list, since CSV needs fixed columns and documents can vary:
mongoexport --uri "$MONGODB_URI" -c orders \
--type csv \
--fields orderId,customerEmail,total,shipping.city,shipping.country,createdAt \
--query '{"status": "shipped"}' \
--out shipped-orders.csv
orderId,customerEmail,total,shipping.city,shipping.country,createdAt
ORD-10492,alice@example.com,214.22,Lyon,FR,2026-09-02T14:10:33.512Z
ORD-10493,bob@example.com,58.00,Austin,US,2026-09-02T14:12:01.004Z
Nested fields use dot notation, and missing fields become empty cells. Arrays and embedded documents that you export as whole fields are written as JSON strings inside the cell, which spreadsheets display but can't do much with. If you need arrays flattened into rows, run an aggregation with $unwind into a temporary collection first (using $out), then export that.
Add --noHeaderLine if the receiving system doesn't want a header row.
Output Options for JSON
--jsonArraywrites a single JSON array instead of one document per line, which some consumers require.--prettyindents the output for human reading. Don't use it for files you plan to re-import in bulk; it makes them larger.--jsonFormatchooses betweenrelaxed(the default) andcanonicalExtended JSON, explained next.
Preserving Types with Extended JSON
JSON only has strings, numbers, booleans, arrays, objects, and null. BSON has ObjectIds, Dates, 64-bit integers, Decimal128, binary data, and more. Extended JSON bridges the gap by wrapping those types in special keys:
{
"_id": { "$oid": "66f12d9a4b1c2d3e4f506172" },
"createdAt": { "$date": "2026-09-02T14:10:33.512Z" },
"total": { "$numberDecimal": "214.22" },
"views": { "$numberLong": "9007199254740993" }
}
mongoexport always writes Extended JSON, and mongoimport always understands it, so a JSON export followed by a JSON import keeps ObjectIds as ObjectIds and Dates as Dates.
The two formats differ in how they handle ordinary numbers:
- Relaxed (default) writes int32 and double values as plain JSON numbers and dates in ISO format. It's more readable and friendlier to other tools, but it can blur the difference between an int and a double, and large 64-bit integers may lose precision when other tools parse them.
- Canonical wraps every number with its exact type, like
{"$numberInt": "42"}and{"$numberDouble": "4.25"}. It's verbose, but it round-trips every type exactly.
Use --jsonFormat canonical when the export is headed back into MongoDB and type fidelity matters. Use relaxed when a human or a non-MongoDB tool will read the output.
CSV has no equivalent. A CSV round trip loses types unless you import it with --columnsHaveTypes, which is one more reason to prefer JSON for moving data between MongoDB deployments.
Moving a Collection Between Clusters
Because mongoexport can write to standard output and mongoimport reads from standard input when --file is omitted, you can pipe one into the other and never touch the disk:
mongoexport --uri "$SOURCE_URI" -c products --jsonFormat canonical \
| mongoimport --uri "$TARGET_URI" -c products --drop
That's a quick way to copy a reference collection from staging to a new development cluster. You can transform in the middle, too. With jq, for instance, you can strip a field before it reaches the target:
mongoexport --uri "$SOURCE_URI" -c users \
| jq -c 'del(.passwordHash)' \
| mongoimport --uri "$DEV_URI" -c users --drop
This is also a useful habit for building development datasets without copying secrets. For more on that workflow, see data seeding and fixtures for development.
Note what this pipeline doesn't copy: indexes, validation rules, collection options, and views. Recreate those separately on the target.
When Not to Use These Tools
They're not a backup solution. mongoexport doesn't capture indexes or collection options, it reads a moving target on a live database without a consistent point-in-time snapshot, and CSV loses types entirely. For backups and full-fidelity migrations, use mongodump and mongorestore, filesystem snapshots, or managed backups on Atlas. The comparison in backup and restore strategies goes through the options.
They're not a transformation engine. Beyond simple type declarations and field selection, complex reshaping belongs in an aggregation pipeline (before exporting) or in your own loader using bulk write operations, where you control validation, upserts, and error handling.
Performance Tips
Increase insertion workers. --numInsertionWorkers runs several insert streams in parallel. On a well-provisioned cluster, --numInsertionWorkers 4 or 8 can speed up large imports noticeably. Check server load while you experiment.
Don't force insertion order unless you need it. --maintainInsertionOrder inserts documents strictly in file order, which disables parallelism. Leave it off for bulk loads.
Build indexes afterwards for initial loads. For a large import into an empty collection, creating secondary indexes after the data is in place is typically faster than maintaining them during the load. Keep any unique index you rely on for --mode upsert matching.
Export from a secondary. For big exports from a replica set, --readPreference secondary keeps the read load off the primary. Expect the data to reflect the secondary's slight replication lag.
Troubleshooting Common Errors
An error about being unable to decode an array. Your file is a JSON array, but you didn't pass --jsonArray. Add the flag, or convert the file to one document per line.
Authentication failed. Check the username, password, and database. For self-hosted users created in admin, add authSource=admin to the URI. On Atlas, confirm the database user exists in the right project.
Connection timeouts to Atlas. Your IP probably isn't on the access list, or a corporate network blocks the port. Try from another network, or add your current IP in the Atlas UI.
Duplicate key errors during import. Documents in the file collide with existing ones on _id or another unique index. Use --drop for a clean reload, or --mode upsert or --mode merge with --upsertFields to update existing documents. Duplicate handling in general is covered in handling duplicate key errors.
Dates imported as strings. Plain JSON strings stay strings. Use Extended JSON {"$date": "..."} in JSON files, or a typed date() column with --columnsHaveTypes in CSV.
Query matches nothing on export. Usually a type mismatch: comparing a date field to a plain string, or an ObjectId to a plain string. Use {"$date": ...} and {"$oid": ...} in --query.
Conclusion
mongoimport and mongoexport are the quickest way to get data in and out of MongoDB when the data already lives in files or needs to end up in one. Use line-per-document JSON by default, add --jsonArray only when the file really is an array, declare column types for CSV with --columnsHaveTypes, pick merge over upsert when a file contains partial documents, and write export queries in Extended JSON. For anything that needs indexes, consistency, or complete type fidelity, reach for mongodump instead.
Next time someone hands you a spreadsheet to load, export it as CSV, add types to the header row (price.decimal(), createdAt.date(2006-01-02)), and import it with --columnsHaveTypes --parseGrace stop. You'll catch every bad row on the first run instead of weeks later.


