MongoDBObjectId Explained

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

JS
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

JS
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
Tip
The _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

JS
// Most-recently-inserted documents first
db.events.find().sort({ _id: -1 }).limit(20)
Warning
Do not rely on 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

JS
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 resolves
ObjectId 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

Note
If your system already generates 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 _id can sometimes double as the shard key.

Custom _id examples

JS
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'