0% completed
What is ObjectId in MongoDB?
On This Page
The Three Parts of an ObjectId
How Unique an ObjectId Is
Reading the Time From an ObjectId
Sorting by ObjectId
ObjectId and Strings
When ObjectId Fits
Who chooses the _id of a document when your insert does not include one? It is usually not the server. The driver, which connects your program to the server, generates a value before it sends the document.
db.users.insertOne({ name: "John Doe", age: 30 })
{ acknowledged: true, insertedId: ObjectId('6ae757159d72706461cb0cea') }
The value is an ObjectId: a 12-byte identifier designed so that any client can create one at any time, with no need to ask the server or another client for the next number. If a document still reaches the server with no _id, for example from a tool that sends the database command directly, the server adds an ObjectId of its own.
ObjectId is only the default type for _id. You can use another value if your data already has a natural identifier, such as { _id: "user-42" } or { _id: 1001 }. In that case, you are responsible for keeping it unique within the collection.
The Three Parts of an ObjectId
An ObjectId is 12 bytes, which mongosh displays as 24 hexadecimal characters. Those bytes have three parts. We will use the value 507f1f77bcf86cd799439011.
The first 4 bytes, 507f1f77, are a timestamp: the number of seconds since the Unix epoch (January 1, 1970) at the moment the value was generated.
The next 5 bytes, bcf86cd799, are a random value. A client process generates it once when it starts, and uses it for all of its ObjectIds. This part separates values generated by different machines and different processes.
The last 3 bytes, 439011, are a counter. It starts from a random value, and the process adds one for each new ObjectId. This part separates values generated by one process within the same second.
You can see the counter by inserting a few documents at once. All three values below came from one process within one second, and only the last characters differ:
db.users.insertMany([{ name: "Alice" }, { name: "Bob" }, { name: "Charlie" }])
{ acknowledged: true, insertedIds: { '0': ObjectId('6ae757159d72706461cb0ceb'), '1': ObjectId('6ae757159d72706461cb0cec'), '2': ObjectId('6ae757159d72706461cb0ced') } }
Older articles describe a different layout, with a 3-byte machine identifier and a 2-byte process identifier in the middle. The current format uses the 5-byte random value in their place, and the middle bytes no longer name a machine or a process.
How Unique an ObjectId Is
Two ObjectIds would be equal only if they had the same second, the same random value and the same counter value. That is extremely unlikely, but it is not impossible, and the format provides no guarantee.
The actual guarantee comes from the server. Every collection has a unique index on _id, which is a structure that rejects a second document with the same value. Values are therefore unique within a collection, whereas an ObjectId is only very likely to be unique across collections and databases.
db.users.insertOne({ _id: ObjectId("507f1f77bcf86cd799439011"), name: "Jane Doe" }) db.users.insertOne({ _id: ObjectId("507f1f77bcf86cd799439011"), name: "Sam" })
The second insert fails with an E11000 duplicate key error.
There is one exception. When a collection is split across several servers, each server checks only its own documents, unless _id is the field that decides how the documents are split. Two servers could then hold the same _id, and the application must keep the values unique. Generated ObjectIds meet that need, which is another reason to keep them as the default.
Reading the Time From an ObjectId
Because the first 4 bytes are a timestamp, getTimestamp() returns a date, with no separate createdAt field needed:
const myId = ObjectId("507f1f77bcf86cd799439011") myId.getTimestamp()
ISODate('2012-10-17T21:13:27.000Z')
This is useful for auditing and debugging, but it has limits. The value records when the client generated the ObjectId, according to the clock of that client, and that can differ from the time of the insert. Its precision is one second. Anyone who can read the identifier can also read this time, and an ObjectId therefore does not hide when a record was created.
Sorting by ObjectId
The timestamp comes first, and unlike other numbers in BSON, it is stored with its most significant byte first. Sorting on _id therefore gives approximately the order of creation. It is only approximate, for two reasons. Values generated within the same second have no guaranteed order between different clients, and different clients can have different clocks. If you need an exact order, store your own field for it.
ObjectId and Strings
An ObjectId is not a string, even though it is displayed like one. This difference causes a common mistake. A web application receives an identifier from a URL as a string, passes it directly to the query, and finds nothing:
db.users.find({ _id: "507f1f77bcf86cd799439011" })
This query returns no documents, even though Jane's _id displays the same characters. The fix is to convert the string to an ObjectId first:
const idString = "507f1f77bcf86cd799439011" db.users.find({ _id: ObjectId(idString) })
[ { _id: ObjectId('507f1f77bcf86cd799439011'), name: 'Jane Doe' } ]
The string must contain exactly 24 hexadecimal characters. Anything else raises an error, and you should validate an identifier from outside your application before you convert it:
ObjectId("xyz")
BSONError: input must be a 24 character hex string, 12 byte Uint8Array, or an integer
The opposite conversion is needed when you send an identifier to a system that does not understand BSON, such as a JSON API. toString() returns the 24 characters:
myId.toString()
507f1f77bcf86cd799439011
When ObjectId Fits
ObjectId is a good default because it takes only 12 bytes in storage and in indexes, it needs no central authority, and new values generally arrive in increasing order. Those properties matter most in a distributed system, where many clients on many machines insert documents at the same time.
Use your own _id when your data already has a stable and unique identifier, since you can then query by it directly and avoid storing a second unique value.
Reading Progress
0%
On This Page
The Three Parts of an ObjectId
How Unique an ObjectId Is
Reading the Time From an ObjectId
Sorting by ObjectId
ObjectId and Strings
When ObjectId Fits