ObjectId
ObjectId is MongoDB default primary key. 12 bytes: 4 timestamp + 5 random + 3 counter. Sortable, unique, and free.
MongoDB — ObjectId
EXAMPLE
// ===== The layout =====
// 12 bytes:
// bytes 0-3 timestamp (seconds since epoch, big-endian)
// bytes 4-8 random per-process value
// bytes 9-11 incrementing counter (per process, seeded random)
//
// Hex string is 24 chars.
// ===== Creating =====
db.users.insertOne({ name: 'Alex' });
// _id auto-generated as ObjectId('...')
// Explicit:
db.users.insertOne({ _id: ObjectId(), name: 'Sam' });
// ===== Extracting the timestamp =====
const id = ObjectId('66236f8a1a2b3c4d5e6f7a8b');
print(id.getTimestamp());
// ISODate('2024-04-20T12:34:50Z') — the embedded create time
// ===== Filtering by time via id =====
// Find docs created in the last hour:
const oneHourAgo = Math.floor(Date.now() / 1000) - 3600;
db.users.find({ _id: { $gt: ObjectId(oneHourAgo.toString(16) + '0000000000000000') } });
// ===== Sorting =====
// _id sort is approximately chronological:
db.events.find().sort({ _id: -1 }).limit(20);
// Newest first, no extra index needed.
// ===== Indexes =====
// _id always has a unique index. You do not create it.
// ===== Driver usage =====
// Node:
import { ObjectId } from 'mongodb';
const id = new ObjectId(); // new
const id2 = new ObjectId('66236f8a1a2b3c4d5e6f7a8b'); // from hex
const id3 = ObjectId.createFromTime(Date.now()/1000); // from time
// Python (pymongo):
from bson import ObjectId
id = ObjectId()
id2 = ObjectId('66236f8a1a2b3c4d5e6f7a8b')
// ===== Comparing IDs =====
// In code, compare via .equals() (cross-driver) or string equality of toHexString().
id.equals(id2);
id.toHexString() === id2.toHexString();
// Direct == fails (it compares objects, not values).
// ===== Don't store as String in your schema if you use Mongo =====
// Keep _id as ObjectId; serialize to string at API boundaries.
// Saves 12 bytes per doc, keeps indexes lean.
// ===== Pitfalls =====
// - Treating ObjectId timestamp as UTC always (it is — UNIX seconds)
// - Using counter or random portions for security (not random enough)
// - Casting ObjectId to ObjectId('') — needs 12 bytes or 24 hex chars
// - Mixing ObjectId and UUID strings in the same field -> queries miss
// - Trusting client-generated _id without server-side validation
// ===== When to override _id =====
// - You want application-meaningful IDs (slugs, UUIDs)
// - You want IDs that are NOT chronologically sortable
// - You want fewer surprises on cross-shard queries
// Otherwise: let Mongo generate ObjectId.
// ===== Patterns to internalise =====
// - Use ObjectId by default; override only with reason
// - Sort by _id desc for 'latest N' lists; no extra index
// - Compare IDs with .equals() / hex string equality
// - Embed timestamps explicitly when business logic needs them (do not rely on _id for that)
Why it matters
ObjectId gives you a unique, sortable, time-embedded primary key for free. Keep it as ObjectId in storage, convert to string at API boundaries. Use it for cheap chronological sorts and as an idempotency-friendly identifier; reach for UUIDs only when application semantics demand them.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// ObjectId — 12-byte unique id with timestamp. const id = new ObjectId(); id.getTimestamp();Try it Yourself »
Discussion
Loading…