UUID v7 vs v4 — picking the right one in 2026
On this page
UUID v7 became part of the official spec in May 2024 with RFC 9562. Before that it was a draft that some libraries shipped early. Now it’s standard, the major databases support it, and there’s no longer a reason to use ULID or KSUID for the “I want a sortable UUID” use case.
But v4 isn’t going anywhere. Picking the right variant is a function of where the ID lives, who reads it, and what your database does with it.
The two-sentence summary
v4 is 122 bits of randomness wrapped in UUID format. Use it when you want IDs that are unguessable and reveal nothing about creation time.
v7 is a 48-bit timestamp followed by 74 bits of randomness. Use it as a database primary key — it sorts in time order, which keeps B-tree indexes happy, and it’s still random enough to be unpredictable in practice.
Why v7 exists at all
Random UUIDs are great for distributed-system uniqueness. They’re terrible as database primary keys.
The reason: every modern database uses a B-tree (or B+tree) for primary key indexes. B-trees insert in sorted order. When you insert a row with a key that’s larger than every existing key, the database appends to the rightmost leaf — fast, predictable, cache-friendly. When you insert a key from anywhere in the range, it has to find the right leaf, split it if it’s full, rebalance the tree.
With auto-increment integers, every insert hits the rightmost leaf. Inserts are essentially free. With v4 UUIDs, every insert hits a random leaf. The database is doing log(n) work per insert plus constant rebalancing.
In practice, on a hot OLTP table:
- Auto-increment int → ~0.05ms per insert
- v4 UUID → ~0.5ms per insert (10× slower at scale)
- v7 UUID → ~0.06ms per insert (close to int)
The numbers depend on the engine, the row size, and the index depth, but the shape is consistent: v7’s time-ordered prefix means inserts cluster at the right edge, just like ints.
This is the reason v7 exists. Not for sortability in your application code — that’s a side benefit. The reason is the database.
When to use v7
- Database primary keys. This is the main case. Postgres, MySQL, SQL Server, SQLite — all have B-tree primary key indexes. v7 keeps them happy.
- Anywhere you’d otherwise reach for ULID, KSUID, Snowflake, or a custom epoch-prefixed format. v7 is now the official version with the same guarantees.
- Event IDs in time-series tables. Same reason as primary keys: the index loves time-ordered inserts.
- Public-facing IDs that are OK to leak creation time. A v7 ID
reveals roughly when the row was created (millisecond precision).
For most product features that’s fine — you can already infer
creation time from
created_atcolumns. For some it’s not.
When to stick with v4
- Session tokens, password reset tokens, API keys. These have to be unguessable. v7 leaks creation time and uses fewer random bits (74 vs 122). For session tokens specifically, you should be using something like 256-bit random values, not UUIDs at all.
- Anonymized IDs. If your goal is “this ID reveals nothing about the user, the time, or the system that issued it”, v4 is the right shape.
- One-shot test fixtures, mock IDs in unit tests. Doesn’t matter what you pick; v4 is fine.
- Anywhere you want IDs that don’t sort. Some access patterns want random distribution — for instance, a sharded key-value store that hashes the ID. There v4’s distribution is a feature.
The footgun: don’t use v4 as your database primary key just because it’s the default in your library. Pick deliberately.
What v7 actually looks like
A v7 UUID has the format:
018f3a4b-7c5d-7000-8abc-def012345678
└──────┬─────┘ └┬┘ └────┬─────────┘
timestamp ver random
(48 bits) (4 bits) (74 bits)
The first 48 bits are milliseconds since the Unix epoch — straight
binary, big-endian. The version nibble is 0x7. The variant bits and
remaining 74 bits are random.
Two consecutive v7 IDs generated milliseconds apart will share the first ~10 hex characters. Two generated in the same millisecond will share the first 12 and differ from there. This is what makes them sort-friendly.
Generating v7 in your app:
- Postgres 18+ —
gen_random_uuid_v7()(native, no extension). - Postgres 14–17 — use the
pg_uuidv7extension or generate in app. - JavaScript —
crypto.randomUUID()returns v4 (the spec didn’t change). Use a library likeuuidv9+ oruuidv7. - Python —
uuidstdlib only does v1/v3/v4/v5; use theuuid7package oruuid6(which exposes both v6 and v7). - Rust —
uuidcrate v1.6+ hasUuid::now_v7()behind thev7feature. - Go —
github.com/google/uuidv1.6+ hasNewV7().
For one-off generation in the browser, see guid.tooljo.com — it does v7 client-side.
What about v6?
UUID v6 is also in RFC 9562. It’s a reordered v1 (which is timestamp
- MAC address). v6 was an intermediate step between v1’s bad properties (leaks MAC address; not random enough) and v7’s good ones.
Use v7. Skip v6 unless you have a specific compatibility need with existing v1 systems.
Common questions
Is v7 random enough? 74 bits of randomness gives ~10^22 unique values per millisecond, which is more than enough for any single generator. Across distributed generators in the same millisecond, collision probability is negligible.
Can I sort v7 UUIDs as strings? Yes, and that’s a deliberate
property of the encoding. 018f3a4b-... < 018f3a4c-... both as
strings and as binary. If you store UUIDs as text columns (don’t —
use the binary type), they’ll still sort correctly.
Will my v4-using database be slow forever? Migrating from v4 to v7 on an existing table is hard — you’d have to rewrite every row’s PK, which means rewriting every index that references it. Most teams just switch new tables to v7 and live with v4 on legacy tables. The cost is real but bounded.
Should I use v7 in URLs? It’s fine, but be aware: a v7 ID in a URL leaks the creation time of the resource to anyone who sees the URL. For most products that’s not sensitive. For some it is.
Default
If you’re starting a new service in 2026 and haven’t picked yet:
- Database primary keys → v7.
- Tokens that have to be unguessable → 256-bit random, not UUIDs.
- Public IDs where time-leak is fine → v7.
- Public IDs where time-leak is not fine → v4.
If a library defaults to v4 for primary keys, treat that as a bug, not a recommendation. The default was set before v7 existed.
Further reading
- RFC 9562 — UUID spec
- Buildkite: why we chose UUIDv7 for database keys
- Generate / inspect v7 in the browser at guid.tooljo.com