UUID v4 vs UUID v7 vs ULID: Which Unique ID Should You Use?
Compare random UUID v4, time-ordered UUID v7 and ULID for database keys, APIs and distributed systems, with performance, privacy and format trade-offs.
· 5 min read
Auto-incrementing integers were the default primary key for decades. They are compact and fast, but they require a central database to hand out numbers, leak how many records you have, and make merging data from multiple systems painful. Globally unique identifiers solve these problems: any server, mobile app or offline client can create an ID that will never collide. The question is which kind. This guide compares the three most popular options in 2026: UUID version 4, UUID version 7 and ULID.
A quick refresher on UUIDs
A UUID (Universally Unique Identifier) is a 128-bit value, usually written as 32 hexadecimal digits in five groups:
550e8400-e29b-41d4-a716-446655440000
The current standard, RFC 9562 (published in 2024, replacing RFC 4122), defines several versions. A few bits encode the version and variant; the rest depends on the version. The third group always starts with the version number, so you can tell a v4 from a v7 at a glance.
UUID v4: purely random
A version 4 UUID contains 122 random bits plus 6 fixed bits for version and variant.
Strengths
- Trivial to generate: every language and database has a built-in function, such as
crypto.randomUUID()in JavaScript orgen_random_uuid()in PostgreSQL. - Reveals nothing: no timestamp, no machine identifier, no sequence.
- Collision probability is negligible. You would need to generate around 2.7 × 10^18 IDs for a 50% chance of a single duplicate.
Weaknesses
- Random order hurts database indexes. B-tree indexes, used by PostgreSQL, MySQL and SQL Server, perform best when new keys arrive in increasing order. Random keys land all over the index, causing page splits, poor cache locality and write amplification. On large tables, insert throughput can drop significantly and indexes grow larger than necessary.
- Not sortable by creation time, so you need a separate
created_atcolumn for chronological queries.
UUID v7: time-ordered
Version 7 places a 48-bit Unix timestamp in milliseconds at the front, followed by 74 bits of randomness (some implementations use part of it as a counter for ordering within the same millisecond).
01928c3e-5b7a-7c4d-9f3e-2a1b4c5d6e7f
└─timestamp─┘ └version 7
Strengths
- New IDs are roughly increasing, so inserts append to the end of the index like auto-increment keys. This keeps indexes compact and writes fast.
- Sorting by ID sorts by creation time, which makes cursor-based pagination simple:
WHERE id > :last_id ORDER BY id LIMIT 50. - It is still a standard UUID: it fits native
uuidcolumns, ORMs and APIs that already accept UUIDs. - You can extract the creation time from the ID for debugging.
Weaknesses
- The creation time is visible to anyone who sees the ID. For most records that is harmless; for some, such as user accounts in a privacy-sensitive product, it may reveal when someone signed up.
- Requires a reasonably accurate clock. Large clock jumps backwards can produce IDs that sort earlier than existing ones; good libraries handle this with monotonic counters.
- Native generation functions are newer. PostgreSQL 18 added
uuidv7(); on older versions, generate in application code.
ULID: sortable and compact text
ULID (Universally Unique Lexicographically Sortable Identifier) predates UUID v7 and uses the same idea: a 48-bit millisecond timestamp followed by 80 random bits. The difference is the text form: 26 characters of Crockford Base32.
01J9Z3K8M2Q7X4V5B6N8P0R2T4
Strengths
- Shorter than the 36-character UUID string and URL-safe.
- Case-insensitive and excludes ambiguous letters (I, L, O, U), so it is easier to read aloud or type.
- Lexicographic string sorting equals chronological sorting, which helps in systems that store IDs as text, such as key-value stores or log file names.
Weaknesses
- Not a UUID, so it does not fit native UUID column types without conversion (although its 128 bits can be stored in one).
- A community specification rather than an IETF standard, with slightly different behaviour between libraries.
- Like v7, it exposes creation time.
Side-by-side comparison
| Property | UUID v4 | UUID v7 | ULID |
|---|---|---|---|
| Size | 128 bits | 128 bits | 128 bits |
| Text length | 36 | 36 | 26 |
| Time-ordered | No | Yes | Yes |
| Leaks creation time | No | Yes (ms) | Yes (ms) |
| Standard | RFC 9562 | RFC 9562 | Community spec |
| Native DB type | uuid |
uuid |
Usually text or binary |
| Index-friendly inserts | Poor | Good | Good |
Storage tips
Whatever you choose, store IDs as 16 bytes, not as text. A 36-character string takes more than twice the space, and every index and foreign key referencing it multiplies the cost. PostgreSQL has a native uuid type; MySQL users can use BINARY(16) with UUID_TO_BIN(uuid, 1), where the second argument reorders v1 timestamps — for v7 use the default ordering, since it is already time-ordered.
Expose IDs in APIs in their canonical text form. Clients should treat them as opaque strings and never parse meaning out of them.
Security considerations
None of these IDs are secrets. Do not use them as password reset tokens or session identifiers just because they are hard to guess. A v4 UUID has enough randomness to be unguessable, but v7 and ULID have predictable timestamp prefixes that reduce effective entropy. For secrets, generate at least 128 random bits with a cryptographic generator and treat them like passwords, ideally storing only a hash — see MD5 vs SHA-256 for which hash to use.
Also consider enumeration. Sequential integers let anyone guess /orders/1001, /orders/1002. Random or time-ordered IDs make that impractical, but they are not a substitute for authorisation checks on every request.
Which should you choose?
- Database primary keys in a new system: UUID v7. You get distributed generation and good index performance with full UUID compatibility.
- Public identifiers where creation time must stay private: UUID v4, or a v7 internal key combined with a separate random public ID.
- Text-based stores, file names or IDs people may type: ULID, for its shorter, case-insensitive form.
- Existing system on v4 with performance problems: consider v7 for new tables; mixing versions in one column is technically fine because both are valid UUIDs.
Generating IDs
For quick tasks — seed data, test fixtures, manual inserts — a browser-based UUID generator can produce up to 1,000 v4, v7 or ULID values at once using crypto.getRandomValues. In application code, use your language's standard library or a well-maintained package and generate IDs as close to record creation as possible.
Summary
UUID v4 is random and private but scatters database indexes. UUID v7 adds a timestamp prefix that keeps inserts fast and IDs sortable while staying a standard UUID. ULID offers the same ordering with a shorter, friendlier text format. For most new applications, UUID v7 is the best default; reach for v4 when timing must stay hidden and ULID when humans or text-based systems handle the IDs.