Loading CSV Into MongoDB With mongoimport and Types
Load a CSV into MongoDB with mongoimport: what its type guessing does to zip codes, how to declare types, skip blanks and upsert on a key, with real runs.

MongoDB has no schema to force on a CSV, which sounds like freedom and turns out to be a different set of traps. A relational table rejects a value that does not fit its column. A collection accepts whatever mongoimport hands it, and the tool makes decisions about types on your behalf. This article runs mongoimport on a three-row file chosen to show those decisions, then fixes them with the typing options, and covers blanks, upserts and what the tool leaves to you. The wider pattern for loading files into a database without surprises is in automating CSV imports into a database.
The file and the default import
Three orders, with a leading-zero zip code, a quoted name containing a comma, a quoted field with escaped quotes and two empty notes:
order_id,customer,zip,placed_on,total,note
1001,Acme Ltd,02134,2026-10-01,120.50,
1002,"Beta, Corp",90210,2026-10-01,89.90,"said ""hi"""
1003,Cy Foods,10001,2026-10-02,15,The simplest import uses the header for field names:
mongoimport --db shop --collection orders_default --type=csv --headerline --file orders-mongo.csvconnected to: mongodb://localhost/
3 document(s) imported successfully. 0 document(s) failed to import.Reading the documents back in mongosh shows what the tool decided (reformatted here to one line per document):
{ order_id: 1001, customer: 'Acme Ltd', zip: 2134, placed_on: '2026-10-01', total: 120.5, note: '' }
{ order_id: 1002, customer: 'Beta, Corp', zip: 90210, placed_on: '2026-10-01', total: 89.9, note: 'said "hi"' }
{ order_id: 1003, customer: 'Cy Foods', zip: 10001, placed_on: '2026-10-02', total: 15, note: '' }Three things happened, and none of them was an error. The quoting was handled correctly: Beta, Corp stayed one field, and the doubled quotes became one. But mongoimport inferred types from the text, so order_id became a number, total a number (15 shown as an integer and 120.5 as a decimal), and zip, a postal code, became the integer 2134 with its zero gone. The date stayed a string, since it has no date inference. And the empty notes were stored as empty strings, not left out. The summary line says all three were imported, and it is accurate.
Every part of that is a decision you may not have wanted. A zip stored as a number cannot be matched to the text 02134 that other systems send, total as a double accumulates float error in sums, and a date stored as a string cannot be queried by range without conversion. The type-guessing is the same hazard as in why pandas turns zip codes into numbers, with the difference that a MongoDB collection will go on accepting inconsistent types forever.
Declare the types
--columnsHaveTypes turns inference off for the fields you describe and applies a type to each. You give the names and types with --fields (or a --fieldFile), and since the header is then redundant, the file should have none, or you should use --headerline with a header edited to carry the types, such as order_id.int32(). Removing the header line and passing the fields:
sed 1d orders-mongo.csv > body.csv
mongoimport --db shop --collection orders_typed --type=csv --columnsHaveTypes \
--fields "order_id.int32(),customer.string(),zip.string(),placed_on.date(2006-01-02),total.decimal(),note.string()" \
--ignoreBlanks --file body.csv3 document(s) imported successfully. 0 document(s) failed to import.The types are int32, int64, double, decimal, boolean, string and date, among others, and a date needs a format given in Go's reference layout (the BSON types reference lists what each becomes), which is why 2006-01-02 appears: it is the layout for YYYY-MM-DD, not a date to change. Checking what was stored with an aggregation that asks for each field's BSON type:
{ order_id: 'int', zip: 'string', placed_on: 'date', total: 'decimal' }and the documents:
{ zip: '02134', placed_on: ISODate('2026-10-01T00:00:00.000Z'), total: Decimal128('120.50') }
{ zip: '10001', placed_on: ISODate('2026-10-02T00:00:00.000Z'), total: Decimal128('15') }
{ zip: '90210', placed_on: ISODate('2026-10-01T00:00:00.000Z'), total: Decimal128('89.90'), note: 'said "hi"' }The zip kept its zero, the date is a real date at midnight UTC and the total is a Decimal128, which keeps 120.50 exactly. Note the time: a date with no zone is stored as UTC midnight, and if the source meant local time, the stored instant is off by the zone offset. That is the mongo form of the problem in handling time zones in CSV timestamps.
Blanks
Notice what is missing from the typed documents: the first two have no note field. That was --ignoreBlanks, which omits any field whose CSV value is empty. Without it, the typed import would have stored note: ''. Which is better depends on the queries. In MongoDB a missing field and an empty string are different values, and {note: {$exists: false}} finds the first kind and {note: ''} the second. If your application treats "no note" as absent, ignore blanks, and it also saves space on sparse files. If it treats empty as a value, leave blanks in. A third option is to import everything and clean up with an update afterward, which keeps the raw load honest.
Numbers with --ignoreBlanks behave sensibly, since an empty total would otherwise fail to parse as a decimal: the field is left out instead of failing the row. That is convenient and it hides a missing amount, so run a count of documents without a total after a load.
Upsert on a key
Importing the same file twice with the default mode appends a second copy of every row, so a rerun doubles the collection. --mode upsert with --upsertFields matches existing documents on the named fields and replaces them, inserting where nothing matches:
printf '1001,Acme Ltd,02134,2026-10-01,125.00,corrected\n1004,Delta Inc,60601,2026-10-03,200,new\n' > fixbody.csv
mongoimport --db shop --collection orders_typed --type=csv --columnsHaveTypes \
--fields "order_id.int32(),customer.string(),zip.string(),placed_on.date(2006-01-02),total.decimal(),note.string()" \
--mode upsert --upsertFields order_id --file fixbody.csv2 document(s) imported successfully. 0 document(s) failed to import.The collection held four documents afterward: the original three, with order 1001 replaced by the corrected row and order 1004 added. Upsert needs the key to be unique in the data, and an index on it in the collection, or each match is a collection scan; create a unique index on order_id first. The shape is the same as the relational upsert in idempotent CSV imports that are safe to run twice, with a document in place of a row. Note that the matched document is replaced by the incoming one's fields, so a field present in the old document and absent from the new one does not survive an upsert.
Options for speed and safety
--numInsertionWorkers runs several insert workers in parallel, which helps on large files when the collection is not the bottleneck. --batchSize sets how many documents go in each batch (the default is 100,000). --stopOnError stops at the first failed document instead of continuing, which suits a feed that should be clean. --drop empties the collection first, which makes a rerun idempotent for a collection that is a full snapshot, and is a destructive flag worth a second look before it goes in a script. --uri takes a connection string for a remote deployment, with credentials kept in a config file or the environment and not on the command line, where they show up in a process list.
When mongoimport is not enough
mongoimport loads documents and does little else. It cannot reshape a row into a nested document: a CSV with address_street and address_city columns comes in flat, and building an address sub-document is a job for an aggregation $project stage or a script after the load. It does not validate beyond type conversion, so rules such as "total is non-negative" and "status is one of five values" belong in the collection's schema validation ($jsonSchema), which rejects violating documents at insert time and shows up as failed documents in the import summary. It reads the file from the local disk, so a CSV in object storage needs a download or a pipe first.
For repeated loads with a ledger, validation and reject files, write the loader in a language with the MongoDB driver, parse with a CSV library and insert with bulk_write. The tool is excellent for a one-off, a seed and a migration, and the code takes over when the job has an owner. The reference for every flag is the mongoimport documentation, and the export side is covered in exporting a MongoDB collection to CSV with mongoexport.


