id Field & ObjectId
_id Field & ObjectId
Level 1 — What Is a Document Database? The mandatory, unique, and immutable primary key field (
_id) present in every MongoDB document, which defaults to a 12-byte binary identifier (ObjectId) containing a creation timestamp.
1. Prerequisites
- Field — The key-value structure of document attributes.
2. Term Category
Core Concept (12-Byte Primary Key): ObjectId is MongoDB's default 12-byte BSON data type used to generate globally unique primary key _id values without central coordination.
3. Explanation
Environment Context
- MongoDB Core (Enforced automatically by the storage engine. If a write query omits the
_idfield, the MongoDB driver or server automatically generates anObjectIdand inserts it before writing to disk).
(1) Design Motivation — "Why did we design this?"
In relational databases, tables use a Primary Key (usually an auto-incrementing integer like id SERIAL or a UUID) to uniquely identify rows.
In MongoDB, we need the same unique identification.
MongoDB enforces a strict rule: every single document in a collection must contain a field named exactly _id.
If you use auto-incrementing integers (1, 2, 3...) in a distributed database:
- If you have 5 database servers handling writes, they must communicate constantly over the network to coordinate which server gets to assign ticket number
42. This network coordination slows down write speeds.
We designed ObjectId as a decentralized, 12-byte binary primary key.
Because of its mathematical formula, any client or database server can generate an ObjectId independently, guaranteeing uniqueness across global clusters without any network coordination overhead.
(2) The 12-Byte Anatomy of an ObjectId
An ObjectId is displayed as a 24-character hexadecimal string (e.g. 60c72b2f9b1d8b2e88a8d1a1), but represents 12 bytes of binary data:
┌──────────────────────┬──────────────────────────┬──────────────────┐
│ Timestamp (4 bytes) │ Random Machine (5 bytes) │ Counter (3 bytes)│
└──────────────────────┴──────────────────────────┴──────────────────┘
- Bytes 1–4 (Timestamp): Seconds since the Unix epoch. This means you can extract the exact creation date/time of a document directly from its
_id! You don't need a separatecreated_atfield if you only need the creation date. - Bytes 5–9 (Random Value): A unique identifier for the machine and process that generated the ID.
- Bytes 10–12 (Counter): An incrementing counter to prevent collisions if the same machine generates multiple IDs in the same second.
(3) Reality Metaphor
Imagine printing tracking barcodes in a shipping logistics company:
- Auto-increment SQL ID: A single mechanical ticket counter. To print a ticket, you must walk to the machine, click the lever, and get the next sequential number. (Bottlenecked).
- ObjectId: A Barcode Formula printed on boxes. The label prints the current time, the printer machine serial number, and a counter tracking how many boxes the printer has processed that second.
- Because the printer ID is baked in, 50 warehouses around the world print barcodes simultaneously without ever duplicate-printing the same number.
(4) Code Examples
Generating and Inspecting ObjectIds in mongosh
// 1. Generate a new ObjectId on-the-fly
const newId = ObjectId();
// Returns e.g. ObjectId("65fc71239b1d8b2e88a8d1a1")
// 2. Extract the embedded timestamp date directly from the ID!
newId.getTimestamp();
// Returns: ISODate("2026-07-21T15:04:35.000Z")
// 3. Query a document by its ObjectId key
db.users.findOne({ _id: ObjectId("65fc71239b1d8b2e88a8d1a1") });
4. Common Mistakes & Pitfalls
Mistake 1: Trying to modify the '_id' field of a document after it has been created
The mistake: Executing an update query to change a user's _id from a legacy key to a new ObjectId:
// BAD: Fails with a database write error!
db.users.updateOne(
{ _id: "old_key" },
{ $set: { _id: ObjectId("65fc71239b1d8b2e88a8d1a1") } }
);
// ERROR: Performing an update on the path '_id' is immutable.
Why it's wrong: The _id field is immutable in MongoDB.
Once a document is written, its primary key index key cannot be changed.
This ensures index integrity and prevents data corruption.
Fix: If you must change a document's _id, you must copy the document data, delete the original document from the collection, and insert a new document containing the modified _id.
Mistake 2: Comparing BSON ObjectId Instances with String "..." Using JavaScript ===
The mistake: Writing if (doc._id === "60d5ecb8b5c9c22b9c8b4567") in Node.js.
Why it's wrong: doc._id is an ObjectId object instance! String comparison ObjectId === string returns false. Use doc._id.equals(str) or doc._id.toString() === str.
Incorrect:
if (user._id === "60d5ecb8b5c9c22b9c8b4567") // ❌ Always evaluates to false!
Fix:
if (user._id.equals("60d5ecb8b5c9c22b9c8b4567")) // Correct BSON ObjectId comparison
Mistake 3: Passing Invalid 24-Character Strings to new ObjectId()
The mistake: Constructing new ObjectId("invalid_string").
Why it's wrong: ObjectId requires a 12-byte binary buffer or a 24-character hexadecimal string. Passing invalid strings throws BSONTypeError.
Incorrect:
new ObjectId("12345"); // ❌ BSONTypeError: Argument passed in must be a 24 char hex string
Fix:
if (ObjectId.isValid(str)) { new ObjectId(str); }
5. Practice Exercises
Exercise 1: Deconstructing BSON ObjectId Structure
Scenario: Deconstruct a 12-byte BSON ObjectId into its 3 constituent component parts.
Requirements:
- Identify 4-byte timestamp, 5-byte random value, and 3-byte counter.
Answer
Implementation
const id = new ObjectId();
console.log("Hex String (24 chars):", id.toHexString());
console.log("Embedded Timestamp:", id.getTimestamp());
Technical Explanation
- Byte 0-3: 4-byte Unix epoch timestamp in seconds.
- Byte 4-8: 5-byte random value unique to machine process.
- Byte 9-11: 3-byte incrementing counter initialized to a random number.
Exercise 2: Extracting Timestamps from ObjectIds
Scenario:
Extract the exact creation timestamp of document user:alice directly from its _id field without storing a separate createdAt field.
Requirements:
- Execute
doc._id.getTimestamp().
Answer
Implementation
const user = db.users.findOne({ email: "alice@example.com" });
console.log("User Created At:", user._id.getTimestamp());
Technical Explanation
getTimestamp()extracts the embedded 4-byte Unix creation timestamp from an ObjectId.- Eliminates the need for a dedicated
createdAtdate field in simple schemas. - Enables sorting by creation order naturally using
_id.
Exercise 3: Time-Range Filtering using ObjectIds
Scenario:
Query documents created after a specific date 2026-01-01 by constructing a target ObjectId threshold.
Requirements:
- Create threshold
ObjectId.createFromTime(timestamp).
Answer
Implementation
const targetDate = new Date("2026-01-01T00:00:00Z");
const targetId = ObjectId.createFromTime(targetDate.getTime() / 1000);
db.logs.find({
_id: { $gte: targetId }
});
Technical Explanation
ObjectId.createFromTime(seconds)constructs an ObjectId threshold corresponding to a target date.- Queries index
{ _id: 1 }directly to filter records by creation time. - Extremely efficient because
_idis always indexed by default.
6. Related Terms
- Field — The parent attribute structure.
- BSON (Binary JSON) — The serialization format.
ObjectIdas a Manual Reference — Related concept:ObjectIdas a Manual Reference.
7. Key Takeaways
- The
_idfield is the mandatory, immutable primary key for every document. ObjectIdis the default 12-byte binary type auto-generated for the_idfield.- Decentralized design allows clients to generate unique IDs without network lag.
- The first 4 bytes of an ObjectId store the creation epoch timestamp.
- Use
.getTimestamp()to extract the creation date, saving separate field writes. - Primary key values are immutable; you cannot update an
_idcolumn value. - If you don't provide
_idat write time, the driver injects an ObjectId.