ObjectId
ObjectId is the default type MongoDB uses for the _id field when you don't supply your own value. It packs a timestamp, machine/process identity, and a counter into 12 bytes, so it's globally unique without any coordination between clients — no auto-increment sequence, no central counter, no round-trip to the server needed before an insert.
The 12-Byte Anatomy
A modern ObjectId (server versions 3.4+) is built from three parts, laid out big-endian:
Bytes | Component | Purpose |
|---|---|---|
0–3 | Unix timestamp (seconds) | When the id was generated — makes ObjectIds roughly time-sortable |
4–8 | Random value | Generated once per process, unique per machine+process, avoids collisions without coordination |
9–11 | Incrementing counter | Starts at a random value, increments per id generated within the same process |
An ObjectId, broken down
ObjectId("64f1a2b3c4d5e6f7a8b9c0d1")
// └──┬───┘└───┬────┘└─┬─┘
// timestamp random counter
// (4 bytes) (5 bytes)(3 bytes)Extracting the Timestamp
Because the first 4 bytes are a creation timestamp, every ObjectId can tell you when it (and therefore the document it belongs to) was created — no separate createdAt field required, if second-level precision is enough.
Deriving a creation date
const id = ObjectId("64f1a2b3c4d5e6f7a8b9c0d1")
id.getTimestamp()
// ISODate("2023-09-02T14:22:11.000Z")
// You can construct a "boundary" ObjectId from any date to use in range queries
const start = ObjectId.createFromTime(Math.floor(new Date("2024-01-01").getTime() / 1000))
db.orders.find({ _id: { $gte: start } }) // all documents created on/after Jan 1, 2024_id: { $gte: start } pattern above is a cheap way to filter "documents created after date X" using the default _id index, with no extra index and no extra createdAt field needed.Sortability
Sorting by _id ascending is approximately the same as sorting by insertion time — approximately, because the random and counter components mean two ObjectIds generated in the same second on different processes are not guaranteed to sort in true creation order.
Natural creation-order sort
// Most-recently-inserted documents first
db.events.find().sort({ _id: -1 }).limit(20)ObjectId ordering for anything requiring exact chronological precision across multiple application servers — clock drift between machines and the second-level timestamp resolution mean ties are possible. Use an explicit Date field with millisecond precision when exact ordering matters.Generating ObjectIds Client-Side
Every official driver can generate an ObjectId locally, before sending the insert to the server. This means your application code can know a document's future _id immediately — useful for building related references before the insert round-trip completes.
Generating an id ahead of insert
const { ObjectId } = require('mongodb')
const orderId = new ObjectId() // generated locally, no server call
await db.collection('orders').insertOne({ _id: orderId, item: "Widget" })
await db.collection('auditLog').insertOne({ orderId, action: "created" })
// orderId is already known and usable before the insert() promise resolvesObjectId vs UUID
ObjectId | UUID (v4) | |
|---|---|---|
Size | 12 bytes | 16 bytes |
Sortable by creation time | Yes (second precision) | No (v4 is fully random) |
Cross-system portability | MongoDB-specific type | Universal — usable across any database or service |
Generation | Any MongoDB driver | Any language/library, MongoDB-agnostic |
Best fit | MongoDB-only systems where creation-time ordering is useful | Multi-database systems or where ids are generated outside MongoDB |
UUIDs upstream (say, a message queue or another database assigns them), it's perfectly fine to use a UUID as _id instead of an ObjectId — just store it as BSON Binary subtype 4 for compact storage rather than as a plain string, which needs 36 bytes instead of 16.When to Use a Custom _id
You already have a natural unique key — a username, SKU, ISO country code, or email — and don't want a redundant secondary unique index.
You need ids generated outside MongoDB to line up (e.g., ids assigned by an upstream microservice or an external system of record).
You want a compound key made of multiple business fields, e.g.
{ warehouse: "YYZ", sku: "WIDGET-42" }, to guarantee natural uniqueness and support efficient range scans on the leading field.You need to shard on a key other than
_id— a composite_idcan sometimes double as the shard key.
Custom _id examples
db.users.insertOne({ _id: "alice@example.com", name: "Alice" })
db.countries.insertOne({ _id: "CA", name: "Canada" })
db.inventory.insertOne({ _id: { warehouse: "YYZ", sku: "WIDGET-42" }, qty: 500 })Inspecting ObjectIds in mongosh
test> const id = ObjectId()
test> id
ObjectId('66a1c2d3e4f5a6b7c8d9e0f1')
test> id.getTimestamp()
ISODate('2024-07-24T18:03:15.000Z')
test> id.toString()
'ObjectId("66a1c2d3e4f5a6b7c8d9e0f1")'
test> id.toHexString()
'66a1c2d3e4f5a6b7c8d9e0f1'