ObjectId vs UUID vs NanoID: Which ID Should You Use?
Use UUID v7 if you are choosing today and nothing forces your hand. It is globally unique, time-ordered so it does not fragment a database index, and readable by every language and datastore without a custom type. The other formats each win in a specific situation, and this guide is about which situation you are actually in.
Four formats come up constantly, and the differences that matter are not the ones the names suggest.
The comparison
| Length | Sortable by time | Leaks | Best for | |
|---|---|---|---|---|
| MongoDB ObjectId | 24 hex chars | Yes, to the second | Creation time | MongoDB _id fields |
| UUID v4 | 36 chars | No | Nothing | Anything public-facing |
| UUID v7 | 36 chars | Yes, to the millisecond | Creation time | New primary keys |
| NanoID | 21 chars (adjustable) | No | Nothing | URLs and share links |
| ULID | 26 chars | Yes, to the millisecond | Creation time | Sortable, compact keys |
When to use each one
Use an ObjectId when you are working with MongoDB. It is the native _id type, the drivers generate it client-side, and every tool in the ecosystem expects it. Generating one yourself is normal practice rather than a workaround — it is how optimistic inserts and offline writes work at all.
Use a UUID v4 when something external expects the RFC format — a Postgres uuid column, a public API contract, a correlation id crossing service boundaries. It leaks nothing, which makes it the safe default for identifiers that appear where strangers can see them.
Use UUID v7 for new primary keys. It keeps the RFC layout and format but puts a millisecond timestamp in the leading bits, so inserts land near each other in the index instead of scattering. It is defined in RFC 9562, which superseded RFC 4122 in 2024.
Use a NanoID when a human will see or type the id. Twenty-one URL-safe characters against a UUID's thirty-six, with no hyphens to escape and comparable collision resistance. This is the right choice for short links and share URLs.
Use a random string when the value is a secret. None of the formats above are secrets, and that is the single most common mistake in this area.
The mistake worth avoiding
An identifier is not a token. ObjectIds embed a predictable timestamp and an incrementing counter, UUID v4 is unguessable in practice but is not specified as a security primitive, and NanoID's length is tuned for collision resistance rather than for resisting an attacker.
If a value grants access to something — a password reset link, an unlisted document URL, an API key — generate it from a cryptographic random source at a length chosen for that job. Every generator on this site draws from the browser's crypto.getRandomValues(), but length and purpose are still yours to choose.
Why random IDs fragment a database index
This is the argument behind UUID v7 and ULID, and it is worth understanding rather than taking on faith.
A B-tree index keeps keys in sorted order. When consecutive inserts carry keys that are close together, they land on the same page, the page fills, and the database moves on to the next one. When the keys are random, every insert lands somewhere unrelated: pages split constantly, the working set stops fitting in cache, and write throughput falls as the table grows.
Time-ordered identifiers restore that locality without giving up global uniqueness — which is the whole point of the newer formats.
Reading the timestamp out of an ObjectId
Because the first four bytes of an ObjectId are a Unix timestamp in seconds, an id carries its own creation date:
new Date(parseInt(id.slice(0, 8), 16) * 1000)
That is useful when you need a created-at value and nobody stored one. It is also the reason an ObjectId in a public URL discloses when the record was created — fine for most applications, a real leak for a few.
Generating them privately
All four generators on this site run entirely in your browser. Nothing is transmitted, so no server has ever seen the values you generate — which is not true of an API-based generator, and matters more than it sounds for identifiers you are about to put into production seed data.
Tools used in this guide
Frequently asked questions
Which ID format should I use by default?
If your database mints ids for you, use what it gives you — an ObjectId in MongoDB, a bigint sequence in Postgres. If you generate ids yourself, UUID v7 is the best general-purpose default: globally unique, time-ordered so it does not fragment an index, and understood by every language and database without a custom type.
Is NanoID safer than UUID?
Neither is a security mechanism. A default NanoID carries about 126 bits of randomness against a UUID v4's 122, so they are effectively equivalent for uniqueness. Both are designed to be unique, not unguessable — anything that grants access needs a purpose-built random token instead.
Do ObjectIds leak information?
Yes, one specific thing: creation time. The first four bytes are a Unix timestamp in seconds, so anyone who sees an ObjectId in a URL learns when the record was created. That is harmless for most applications and a genuine disclosure for a few. UUID v4 leaks nothing.
Why do random UUIDs slow down database inserts?
A v4 UUID is random, so consecutive inserts land in unrelated places in a B-tree index. The index splits pages constantly and stops fitting neatly in cache. Time-ordered identifiers — UUID v7, ULID, or an ObjectId — insert near each other and avoid the problem.
How likely is a NanoID collision?
At the 21-character default, generating a million IDs per second would take roughly a thousand years to reach a 1% chance of one collision. That margin disappears fast if you shorten it: at 8 characters you should expect a collision within a few million IDs.