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
- The driver serializes your JavaScript object to BSON. If
_idis missing, it generates anObjectIdat this point — before any network call. - The command is sent to the primary node of the replica set (or the standalone
mongod). - If the collection has a schema validator, the server checks the document against it (unless
bypassDocumentValidationis set); a failing document is rejected with no write performed. - The server checks the unique index on
_idfor a collision. If another document already has that_id, the operation fails with aDuplicateKeyerror (code 11000) and nothing is written. - The document is written to the WiredTiger storage engine and appended to the journal, satisfying durability on the primary.
- If
writeConcernrequires it (e.g.{ w: "majority" }), the primary waits for secondaries to replicate the oplog entry before acknowledging the write back to the client. - The driver returns the
InsertOneResultwithacknowledgedandinsertedId.
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
_idas anObjectIdunless you have a specific reason to supply your own (e.g. a natural key you already enforce as unique). - Catch and handle the
E11000duplicate 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 forinsertManywhen inserting more than one document at a time. - When reading an
_idfrom external input (URL, form, API payload), always wrap it innew ObjectId(...)before querying.
Practice Exercises
- Insert a document into a new
db.productscollection with fieldsname,price, andtags(an array of strings), without supplying an_id. Confirm the returnedinsertedIdis anObjectId. - Insert two documents into
db.accountsusing the same_idvalue on purpose, one after the other. Observe the exact error message and code returned for the second call. - Insert a document into
db.sessionswith an explicit numeric_idand awriteConcernof{ w: "majority" }. Then try tofindOneit using its_idas 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, andDecimal128. - If you omit
_id, the driver generates anObjectIdclient-side before the write is sent. - A duplicate
_idalways fails with anE11000error —insertOnenever overwrites. writeConcerncontrols how durable the acknowledgment is across a replica set.- Passing an array to
insertOneis a bug; useinsertManyinstead. - Always convert string IDs to
ObjectIdbefore querying by_id.
