BlogDeveloper

UUID v4 vs v7: which version to use for IDs

Random v4 UUIDs and time-ordered v7 UUIDs solve different problems. Why v7 is usually the better primary key, when v4 is still right, and what the version bits actually mean.

You need an identifier for a new row, a new file, or a message on a queue. The default reach is for a UUID — 128 bits, generated anywhere without a central counter, almost certainly unique. But "a UUID" isn't one thing. The version you pick changes how the value sorts, how well it behaves as a database key, and what it leaks. The two that matter in 2026 are v4 (random) and v7 (time-ordered), and they are not interchangeable.

What a UUID actually is

A UUID is 128 bits, usually written as 32 hex digits in the 8-4-4-4-12 grouping: 0190b8e4-.... Six of those bits are fixed overhead. Four bits encode the version (which generation scheme produced it) and two encode the variant (the layout standard). The remaining 122 bits are where the versions differ — random noise, a timestamp, or some mix. When you read a UUID, the version digit is the 13th hex character; a 4 there means v4, a 7 means v7.

0190b8e4-5e3a-7c2d-9f10-3a4b5c6d7e8f
                ^ version (7)

That single digit is the whole story. Everything below is about what fills the other bits and why it matters.

v4: random

A v4 UUID is 122 bits of randomness with the version and variant bits stamped in. There is no structure, no timestamp, no machine identifier — just entropy from a cryptographically secure source. That gives it two strong properties: you can generate one anywhere with zero coordination, and you cannot guess the next one from the last.

The cost shows up when v4 is used as a database primary key. Most databases store the primary key in a B-tree, which keeps rows ordered by key on disk. Random keys land in random positions, so inserts scatter writes across the whole index, cause page splits, and thrash the buffer cache. On a busy table this is a measurable throughput and storage hit — the index fragments and grows faster than an ordered one. For a few thousand rows you'll never notice; at millions of inserts you will.

So v4 is the right choice when unguessability is the point: a password reset token in a URL, a public object ID you don't want enumerable, an API key. It's the wrong default for a high-write primary key.

v1 and the detour through time

Before v7, the time-based option was v1: a 60-bit timestamp plus the machine's MAC address. Two things sank it. The MAC address leaks which machine minted the ID, and the timestamp bytes are stored in a scrambled order, so v1 values don't sort chronologically as strings or bytes. It solved "encode the time" but not "be sortable," which is the property people actually wanted. Treat v1 as legacy.

v7: time-ordered

A v7 UUID puts a 48-bit Unix timestamp in milliseconds at the front, then fills the rest with randomness. Because the timestamp is the most significant part and stored big-endian, v7 values sort chronologically by their raw bytes — and therefore by their string form too. Two IDs generated in order will sort in that order.

That one change fixes the B-tree problem. New keys are larger than old ones, so inserts append near the right edge of the index instead of scattering. You keep the "generate anywhere, no coordination" property of v4 — the trailing random bits still make collisions within the same millisecond vanishingly unlikely — while getting index locality and free chronological ordering. For a primary key, that combination is why v7 has become the default recommendation.

v4 v7
Content 122 random bits 48-bit ms timestamp + random
Sortable by time No Yes (byte and string order)
Index-friendly as PK No (scatters writes) Yes (near-sequential)
Reveals creation time No Yes
Good for opaque/unguessable tokens database primary keys

The trade-off v7 makes

v7 is sortable because it embeds the creation time, and that's also its one leak: anyone holding a v7 UUID can read, to the millisecond, when it was created. For a primary key that's usually harmless and often useful. For a public token where you don't want to expose timing — a coupon code, an invite link, anything enumerable-adjacent — that leak is a reason to stay on v4. Pick the version by what the ID is exposed to, not by habit.

If you've seen ULID, it's the same idea as v7 — millisecond timestamp prefix plus randomness — predating v7's standardization, with a different text encoding (Crockford base32, 26 characters). v7 is the RFC-blessed form and interoperates as a normal UUID, so for new work there's little reason to choose ULID over it.

Reading and generating them

To inspect a UUID, look at the version digit first, then decide what the rest means. A v7's leading bytes decode to a timestamp; a v4's don't decode to anything. To generate them, modern runtimes are catching up: crypto.randomUUID() in browsers and Node still emits v4, while v7 is available through libraries (uuid on npm, uuid6/uuid7 in other ecosystems) and increasingly in databases directly. Our UUID generator produces both versions in the browser so you can copy a batch without pulling in a dependency.

One thing neither version does is authenticate anything. A UUID is an identifier, not a secret you can verify — if you need a token that proves it was issued by you, that's a signature, covered in JWT signing algorithms. And a UUID is not a hash; if you're reaching for one to fingerprint content, the distinction in hashing, encryption, and encoding is the one you want.

The short version: reach for v7 for primary keys and anything you store in volume, and v4 when the ID is public and the time of creation is nobody's business.