insertOne

insertOne() is the method you use to add a single new document to a MongoDB collection. It is the most fundamental write operation in MongoDB: almost every application eventually calls it to create a user, an order, a log entry, or any other single record. Understanding exactly what it does — how _id is generated, what it returns, and when it fails — is essential before you touch insertMany, updateOne, or transactions.

Overview / How it works

In MongoDB, a collection is a group of documents, and a document is a single BSON object — not plain JSON. BSON (Binary JSON) is a binary-encoded superset of JSON that adds types JSON has no native concept of: ObjectId, Date, Decimal128, distinct 32-bit and 64-bit integers, binary data, and more. When you write { age: 29 } in mongosh, the shell parses that JavaScript object and the driver serializes it to BSON before sending it to the server. This is why you can store a real Date object or an ObjectId directly in a field — something a plain JSON API could never do.

Every document in a collection must have a unique _id field, which acts as the document’s primary key. If you don’t supply one, the driver generates an ObjectId for you automatically before the document is sent to the server. An ObjectId is a 12-byte value that embeds a 4-byte creation timestamp, making it roughly sortable by insertion time even without a separate createdAt field. The _id field is backed by a unique index that MongoDB creates automatically, which is why inserting a document with a duplicate _id is rejected — more on that below.

A single call to insertOne() is atomic: the document is either written completely or not at all, there is no in-between state visible to other readers. This is one reason MongoDB emphasizes embedding related data in one document where practical — a single-document write gives you atomicity for free, without needing a multi-document transaction. If the target collection doesn’t exist yet, MongoDB creates it implicitly on the first insert, along with the default _id index.

MongoDB’s schema flexibility means the collection doesn’t enforce a fixed set of fields across documents — two documents in db.users can have different shapes. That flexibility is a feature for iterating quickly, but it is not a license for chaos: production applications almost always want some consistency, enforced either at the application layer or with MongoDB’s schema validation (a $jsonSchema validator attached to the collection) so that a typo or a missing required field doesn’t silently corrupt data.

Syntax

db.collection.insertOne(
  <document>,
  {
    writeConcern: <document>,
    bypassDocumentValidation: <boolean>,
    comment: <any>
  }
);
Parameter Description
document Required. The BSON/JS object to insert. If it has no _id field, the driver generates an ObjectId for it.
writeConcern Optional. Controls how many replica set members must acknowledge the write before it’s considered successful, e.g. { w: "majority" }. Defaults to the connection’s configured write concern (usually { w: 1 }).
bypassDocumentValidation Optional boolean. If true, skips any JSON Schema validation rules configured on the collection. Use sparingly.
comment Optional. Attaches an arbitrary value to the operation, useful for finding it later in logs or db.currentOp().

insertOne() returns an InsertOneResult object with two fields: acknowledged (whether the server confirmed the write per the write concern) and insertedId (the _id of the new document, whether you supplied it or MongoDB generated it).

Examples

Example 1: A simple insert

use shop
db.users.insertOne({
  name: "Priya Sharma",
  email: "priya.sharma@example.com",
  age: 29,
  createdAt: new Date()
});

Output:

{
  acknowledged: true,
  insertedId: ObjectId('66b1f2a1c3d4e5f6a7b8c9d0')
}

No _id was provided, so the driver generated an ObjectId client-side and returned it in insertedId. That value now uniquely identifies this document for future find, updateOne, or deleteOne calls.

Example 2: Inserting a document with nested data

Because MongoDB documents can contain nested objects and arrays, you can model an entire order — including its line items and shipping address — as one document, which you can then write and read atomically:

db.orders.insertOne({
  customerId: ObjectId("66b1f2a1c3d4e5f6a7b8c9d0"),
  status: "processing",
  items: [
    { sku: "SKU-100", qty: 2, price: 19.99 },
    { sku: "SKU-204", qty: 1, price: 49.5 }
  ],
  shippingAddress: {
    line1: "221B Baker Street",
    city: "London",
    postalCode: "NW1 6XE"
  },
  placedAt: new Date()
});

Output:

{
  acknowledged: true,
  insertedId: ObjectId('66b1f2a2d4e5f6a7b8c9d0e1')
}

Here customerId references the user from Example 1 by its ObjectId, while items and shippingAddress are embedded directly since they belong to this order, don’t grow unboundedly, and are always read together with it.

Example 3: Supplying your own _id and a write concern

db.users.insertOne(
  { _id: 1001, name: "Arjun Mehta", email: "arjun@example.com" },
  { writeConcern: { w: "majority" } }
);

Output:

{ acknowledged: true, insertedId: 1001 }

You can use any unique BSON value as _id — a number, a string, even a compound object — not just an ObjectId. Here we also asked the server to wait until a majority of replica set members acknowledged the write before returning, trading a little latency for stronger durability.

How it works step by step

  1. The driver serializes your JavaScript object to BSON. If _id is missing, it generates an ObjectId at this point — before any network call.
  2. The command is sent to the primary node of the replica set (or the standalone mongod).
  3. If the collection has a schema validator, the server checks the document against it (unless bypassDocumentValidation is set); a failing document is rejected with no write performed.
  4. The server checks the unique index on _id for a collision. If another document already has that _id, the operation fails with a DuplicateKey error (code 11000) and nothing is written.
  5. The document is written to the WiredTiger storage engine and appended to the journal, satisfying durability on the primary.
  6. If writeConcern requires it (e.g. { w: "majority" }), the primary waits for secondaries to replicate the oplog entry before acknowledging the write back to the client.
  7. The driver returns the InsertOneResult with acknowledged and insertedId.

Common Mistakes

Mistake 1: Passing an array to insertOne

// Wrong: insertOne only accepts a single document
db.users.insertOne([
  { name: "Alice" },
  { name: "Bob" }
]);
// MongoServerError: BSONObj not valid for storage

insertOne expects exactly one document object; passing an array causes an error because the array itself gets treated as the document body. Use insertMany for multiple documents:

db.users.insertMany([
  { name: "Alice" },
  { name: "Bob" }
]);

Mistake 2: Assuming a duplicate _id will overwrite the existing document

// Wrong: this does NOT update the existing user with _id 1001
db.users.insertOne({ _id: 1001, name: "Arjun Updated" });
// MongoServerError: E11000 duplicate key error collection: shop.users index: _id_

insertOne never updates — it only creates. Reusing an existing _id always throws E11000, and the document is left untouched. If your intent is “insert if missing, otherwise update,” use updateOne with upsert: true, or findOneAndUpdate:

db.users.updateOne(
  { _id: 1001 },
  { $set: { name: "Arjun Updated" } },
  { upsert: true }
);

Mistake 3: Comparing a string to an ObjectId

// Wrong: idFromUrl is a plain string, _id is stored as an ObjectId
const idFromUrl = "66b1f2a1c3d4e5f6a7b8c9d0";
db.users.findOne({ _id: idFromUrl }); // returns null, even though the document exists

A route parameter or form field is always a string, but the stored _id is a BSON ObjectId — the two types never compare equal. Convert the string first:

db.users.findOne({ _id: new ObjectId(idFromUrl) });

Best Practices

  • Let MongoDB generate _id as an ObjectId unless you have a specific reason to supply your own (e.g. a natural key you already enforce as unique).
  • Catch and handle the E11000 duplicate key error explicitly in application code instead of assuming inserts always succeed.
  • Attach a JSON Schema validator to collections that need shape guarantees, rather than relying on discipline alone.
  • Use writeConcern: { w: "majority" } for writes where durability across a replica set matters more than shaving milliseconds of latency.
  • Embed data you always read together with the parent document (like order line items); reference data that is large, shared, or grows without bound (like a product catalog).
  • Never pass an array to insertOne — reach for insertMany when inserting more than one document at a time.
  • When reading an _id from external input (URL, form, API payload), always wrap it in new ObjectId(...) before querying.

Practice Exercises

  • Insert a document into a new db.products collection with fields name, price, and tags (an array of strings), without supplying an _id. Confirm the returned insertedId is an ObjectId.
  • Insert two documents into db.accounts using the same _id value on purpose, one after the other. Observe the exact error message and code returned for the second call.
  • Insert a document into db.sessions with an explicit numeric _id and a writeConcern of { w: "majority" }. Then try to findOne it using its _id as a string versus as a number, and note which one matches.

Summary

  • insertOne() adds exactly one document to a collection and is atomic for that document.
  • Documents are BSON, not plain JSON — supporting extra types like ObjectId, Date, and Decimal128.
  • If you omit _id, the driver generates an ObjectId client-side before the write is sent.
  • A duplicate _id always fails with an E11000 error — insertOne never overwrites.
  • writeConcern controls how durable the acknowledgment is across a replica set.
  • Passing an array to insertOne is a bug; use insertMany instead.
  • Always convert string IDs to ObjectId before querying by _id.