ObjectId structure
An ObjectId is 12 bytes, usually shown as 24 hexadecimal characters:
507f1f77 bcf86cd799 439011
|--------| |----------| |------|
timestamp random counter
4 bytes 5 bytes 3 bytes
(seconds) (per process) (incrementing)
Because the timestamp comes first, ObjectIds sort roughly by creation time. That is why sorting by
_id is a cheap way to get documents in insertion order.
Get the timestamp in code
MongoDB Shell (mongosh)
// mongosh
ObjectId("507f1f77bcf86cd799439011").getTimestamp()
// ISODate("2012-10-17T21:13:27.000Z")
// Documents created since a date, without a createdAt field
const since = ObjectId.createFromTime(new Date("2026-01-01") / 1000);
db.orders.find({ _id: { $gte: since } });
Node.js driver
import { ObjectId } from 'mongodb';
new ObjectId('507f1f77bcf86cd799439011').getTimestamp();
// 2012-10-17T21:13:27.000Z
ObjectId.createFromTime(Math.floor(Date.now() / 1000));
Python (PyMongo)
from bson import ObjectId
from datetime import datetime, timezone
ObjectId("507f1f77bcf86cd799439011").generation_time
# datetime.datetime(2012, 10, 17, 21, 13, 27, tzinfo=<bson.tz_util.FixedOffset ...>)
ObjectId.from_datetime(datetime(2026, 1, 1, tzinfo=timezone.utc))
Aggregation pipeline
db.users.aggregate([
{ $project: { createdAt: { $toDate: "$_id" } } }
])
Caveats
- The timestamp reflects when the ID was created, which is not always when the document was saved, for example if an app pre-generates IDs or imports old data with new IDs.
- Precision is one second. Several documents created in the same second are ordered by the counter.
-
Custom
_idvalues (strings, numbers, UUIDs) don't carry a timestamp. For time-sortable UUIDs, see UUID v7.
Frequently asked questions
Does a MongoDB ObjectId contain the creation date?
Yes. The first 4 bytes (8 hex characters) of every ObjectId are a Unix timestamp in seconds, recording when the ID was generated, which is usually when the document was inserted. Paste an ObjectId above to read it.
How do I query MongoDB documents by date using _id?
Build an ObjectId whose timestamp is your boundary date and whose other bytes are zero, then use it in a range query: { _id: { $gte: ObjectId("…") } }. The Date → ObjectId converter above builds it for you and shows the full query. It uses the default _id index, so it is fast.
How accurate is the timestamp?
It has 1-second precision and comes from the clock of the machine that generated the ID, usually your application server. The driver creates the ID, not the database. If you need millisecond precision or the server’s time, store a separate createdAt field.
What are the other 8 bytes of an ObjectId?
Since MongoDB 3.4: a 5-byte random value generated once per process, followed by a 3-byte counter that starts at a random value and increments for each new ObjectId. Older ObjectIds used a machine ID and process ID instead of the random value.
Can I get an ObjectId’s date in an aggregation pipeline?
Yes. Use { $toDate: "$_id" } (MongoDB 4.0+) to convert the _id to a Date, which you can then group or filter by.