StringToolsStringTools

UUID Generator: v4, v7, and ULID

Generate UUID v4, time-ordered UUID v7, ULIDs, and short IDs in batches of up to 1,000, using your browser’s cryptographic random source. The interesting question is not how to make one — it is which kind to make, because v7 changed the usual answer.

1 to 1,000 per batch

Randomness
122 random bits

From your OS’s cryptographic RNG

Format
0 UUID v4sClick any value to copy it

🎲 Pick v4 when the ID is public

A v4 is 122 bits of pure randomness. It reveals nothing — not when it was made, not what machine made it, not how many came before it. That makes it the right choice for anything a stranger will see: URLs, API object IDs, invite tokens, file names. The cost is that consecutive v4s land in random places in a B-tree index, so a table keyed on them fragments its index and its page cache as it grows.

⏳ Pick v7 for database primary keys

A v7 puts a 48-bit millisecond timestamp in front, so new rows always append to the right edge of the index instead of scattering. You get the write behaviour of an auto-increment ID with the global uniqueness of a UUID, and sorting the IDs as plain strings sorts them by creation time — handy for pagination and event logs. The trade-off is the timestamp it carries in the open.

Comparison of UUID v4, UUID v7, ULID and short IDs
FormatLengthSorts by timeRandom bitsIDs before a 1% collision riskBest for
🎲 UUID v436 (32 hex + 4 hyphens)No1223.3 × 10¹⁷Public IDs, tokens, anything where the creation time must stay private
UUID v736 (32 hex + 4 hyphens)Yes741.9 × 10¹⁰Database primary keys, event logs, high-insert-rate tables
🔤 ULID26 (Crockford base32)Yes801.6 × 10¹¹Shorter sortable IDs that stay readable and URL-safe
✂️ Short IDyour choice (21 by default)No1261.3 × 10¹⁸Short links, invite codes, share slugs

Every ID on this page is created inside your browser using crypto.randomUUID() and crypto.getRandomValues()— your operating system’s cryptographic random number generator. Nothing is sent to a server, nothing is logged, and no ID leaves this tab unless you copy it. Math.random() is never used: it is not cryptographically secure, and its future output can be reconstructed from a few samples. UUID v4 and v7 follow RFC 9562 (which replaced RFC 4122), including the version nibble and the 10xx variant bits; v7 and ULID both use a monotonic counter so IDs minted in the same millisecond still sort in the order they were created. The collision column is the birthday bound and assumes a perfect random source — treat it as an order of magnitude, not a guarantee.

TL;DR

Use v7 for database primary keys — its 48-bit millisecond prefix makes new rows append to the right edge of the index instead of scattering through it. Use v4 for anything a stranger sees — 122 bits of pure randomness that reveal nothing at all. The catch is that v7 and ULID carry a readable creation timestamp, so an ID tells you roughly when its record was made. Collisions are not the thing to worry about: at a billion v4s per second you would need about a decade before there was even a 1% chance of one. And if your current schema works, leave it alone — v7 is a choice for new tables, not a reason to rewrite old ones.

Which ID should you actually use?

For years the choice was binary: an auto-increment integer that was fast but tied you to one database, or a UUID v4 that was globally unique but hostile to your index. UUID v7 collapsed that trade-off. It puts a Unix millisecond timestamp in the first 48 bits and fills the rest with randomness, so it behaves like a sequence on insert while staying unique across every machine that generates one. ULID does the same thing with a shorter, more readable text encoding.

The honest summary is that there is no single winner. Each format trades one property for another, and the right pick depends on whether the ID is an internal key or a public handle:

UUID v4, UUID v7, ULID and auto-increment integers compared on sortability, randomness, index behaviour and what each one leaks
FormatSizeSorts by timeRandom bitsIndex behaviour on insertWhat it leaks
UUID v416 bytes · 36 charsNo122Lands on a random leaf page every time — splits pages, spreads the working setNothing
UUID v716 bytes · 36 charsYes — to the millisecond74Appends near the right edge, like a sequenceCreation time (ms)
ULID16 bytes · 26 charsYes — to the millisecond80Appends near the right edge; sorts correctly as plain text tooCreation time (ms)
Auto-increment integer4 or 8 bytesYes — by insert order0Appends at the right edge; the cheapest option there isRow count, creation order, and how fast you are growing

“Random bits” counts only the unpredictable part. A UUID is 128 bits, but v4 spends 6 of them on the version and variant markers, and v7 spends 48 more on the timestamp and 6 on the markers. The auto-increment row is a reminder that a sequential integer is still the fastest key available — it simply cannot be generated by a client, merged across shards, or exposed publicly without telling people how many rows you have.

Why a random primary key makes a database work harder

Nearly every relational database stores its primary key in a B-tree: a sorted structure made of fixed-size pages, usually 8 or 16 KB each. Inserting a row means finding the leaf page where that key belongs and writing it there. Where the key belongs is entirely decided by its value — which is exactly why the shape of your ID matters.

A v4 is uniformly random, so consecutive inserts land on unrelated leaf pages scattered across the whole tree. Two things follow. First, each of those pages has to be in memory to be written to, and once the index outgrows the cache, a large share of inserts turns into a random read from disk first. Second, when a target page is already full the database has to split it — allocate a new page and move roughly half the rows across. Random inserts trigger splits all over the tree, which leaves pages around half empty. The index ends up noticeably larger than the data it indexes, and the fragmentation makes range scans read more pages to return the same rows.

A v7 inserts at the right edge instead. Because the leading bits are a millisecond timestamp, every new key is greater than almost everything already in the tree, so it goes into the rightmost leaf page — which is the one page guaranteed to be hot in cache. Full pages split at the edge rather than in the middle, so they fill up densely instead of settling at half capacity. That is the whole mechanism: not a faster ID, just an ID whose ordering matches the order you write rows in.

How much this bites depends on your engine

In MySQL/InnoDB the primary key is a clustered index — the table rows physically live in primary-key order, and every secondary index stores a full copy of the primary key as its row pointer. A random 16-byte key therefore reorders your actual table data and inflates every other index on it. PostgreSQL stores rows in an unordered heap with the primary key as an ordinary B-tree, so a random key hurts that one index rather than the table itself — the effect is real but milder. And if you store UUIDs as CHAR(36) text rather than a native uuid column or BINARY(16), you are paying more than twice the bytes per key before any of this even starts.

The size of the win varies enormously — with the table’s size, the write rate, the cache-to-index ratio, and the storage underneath. On a small table that fits entirely in memory, the difference is close to nothing. Benchmark your own workload before you quote a number; the mechanism above is what is actually going on, and it is worth more than somebody else’s percentage.

The versions in practice, and why v1 is a liability

A UUID’s version is a single hex digit — the first character of the third group, so 018f4c...-7b3a-... is a v7. It tells you how the other 122 bits were chosen, and the versions are genuinely different animals rather than incremental improvements.

  • v1 — timestamp plus MAC address. The original design encodes a 60-bit timestamp and the generating machine’s network card address. That means a v1 you publish tells anyone who reads it both when it was created and which physical machine created it — the detail that helped identify the author of the Melissa virus in 1999. Some libraries substitute a random node ID, and many do not. Treat any v1 in a public field as a small information leak, and note that its timestamp field is laid out so that v1s do not sort chronologically as bytes.
  • v3 and v5 — hashes of a name. Deterministic: the same namespace plus the same name always produces the same UUID (v3 uses MD5, v5 uses SHA-1). Useful when you need a stable ID derived from something you already have, such as a URL. Not random, not secret, and reversible by anyone who can guess the input.
  • v4 — 122 random bits. No structure, no time, no machine identity. The safest default for anything public, and still the right answer whenever the ID must not say anything about the record behind it.
  • v6, v7 and v8 — the 2024 additions. RFC 9562, published in May 2024, replaced the old RFC 4122 and standardised three new versions. v6 is v1 with the timestamp bits rearranged so it sorts properly, meant as a migration path for existing v1 systems. v7 is the one worth knowing: a 48-bit Unix millisecond timestamp followed by randomness, no MAC address anywhere. v8 is a deliberately free-form slot for custom layouts. v7 being in a published RFC is what moved it from a clever trick to something you can defend in a design review.

ULID is not a UUID at all — it is a separate community specification with the same 128 bits, written in Crockford base32 as 26 characters. Dropping I, L, O and U from the alphabet means it cannot be misread over the phone or accidentally spell a word, and because base32 preserves ordering, a plain alphabetical sort of ULID strings is a chronological sort. That last property is why some teams prefer it over v7 in systems where IDs are compared as text.

The trade-off nobody mentions: a v7 tells you when

Sortability is not free. The first 48 bits of a v7 and the first 10 characters of a ULID are a plain Unix millisecond timestamp — not encrypted, not hashed, just there. Anyone holding one of these IDs can read the creation time out of it in one line of code. The generator above does exactly that: pick v7 or ULID and it shows you the timestamp it just decoded from the first ID in the batch.

Whether that matters depends on where the ID goes. Inside your database, it is harmless and often useful — you get an approximate created_at for free, and you can paginate by ID instead of by timestamp. The moment the same ID appears in a URL, an API response, a webhook payload, or an email link, you have published a fact about the record: when it was made. A user ID reveals the signup date. An order ID reveals when the order was placed. Two invoice IDs from the same customer reveal the gap between their purchases.

A v7 does not leak the way an auto-increment integer does. Sequential integers let anyone count your records and measure your growth rate by ordering two IDs a week apart — the classic enumeration problem. A v7 gives away timing but not volume, because the random tail means you cannot tell how many IDs sit between two of them. It sits between v4 and a sequence, and that is the point: it is a middle position, not a strictly better one.

The pattern that gets you both

Use two IDs. Keep a v7 as the internal primary key, where its ordering earns its keep, and give each row a separate v4 as its public-facing identifier — the one that goes in URLs and API responses. You get the index behaviour internally and leak nothing externally, at the cost of one extra indexed column. If that feels like too much machinery for your app, it probably is; a v4 primary key on a table that will never hold tens of millions of rows is a perfectly reasonable thing to ship.

Collision risk, with the actual numbers

This is the most over-worried question in the topic, so here is the arithmetic. The relevant maths is the birthday problem: with a space of 2n values, the number of IDs you can draw before the probability of any duplicate reaches 1% is roughly √(2 × 2n × ln(1/0.99)). Applied to a 122-bit v4 that gives a number most people find hard to believe:

Number of identifiers that can be generated before a 1% chance of a collision, by bits of randomness
IdentifierRandom bitsIDs before a 1% collision chanceWhat that means in practice
UUID v4122≈ 3.3 × 1017A billion new UUIDs every second for ten years straight
ULID80≈ 1.6 × 1011160 billion, but only counting IDs made in the same millisecond
UUID v774≈ 1.9 × 101019 billion inside one millisecond — different milliseconds cannot collide at all
Short ID, 21 chars126≈ 1.3 × 1018Slightly stronger than a v4, in 15 fewer characters
Short ID, 16 chars96≈ 4.0 × 1013Comfortable for share links at internet scale
Short ID, 12 chars72≈ 9.7 × 109Fine up to hundreds of millions of IDs
Short ID, 10 chars60≈ 1.5 × 108Add a uniqueness constraint and retry on conflict
Short ID, 8 chars48≈ 2.4 × 106Collisions become a real operational concern
Short ID, 6 chars36≈ 37,000One-off promo codes only — never an identity you must not duplicate

Short IDs here use the 64-character URL-safe alphabet this generator produces, so each character carries exactly 6 bits. Two caveats worth stating plainly. The numbers assume a genuinely uniform random source; this tool uses crypto.getRandomValues() and crypto.randomUUID(), but a library built on Math.random() has nothing like this much real entropy, and that — not the maths — is where UUID collisions in the wild actually come from. And for v7 and ULID the row is a per-millisecond figure, because two IDs with different timestamps can never be equal; the practical risk there is a machine whose clock jumps backwards, not exhausted randomness.

What this generator does, and what it deliberately does not

Everything above runs inside your browser tab. IDs are produced by your operating system’s cryptographic random number generator through crypto.randomUUID() and crypto.getRandomValues(); nothing is sent to a server, nothing is logged, and no ID leaves the page unless you copy it. There are no accounts and nothing is saved — reload and the batch is gone. If a browser does not expose crypto.getRandomValues, the tool says so and refuses to generate rather than falling back to Math.random(), which is predictable from a handful of samples and has no business producing identifiers.

The limits are worth knowing before you rely on it. Batches are capped at 1,000 IDs, and only the first 200 are listed on screen — “Copy all” still copies every one. It generates v4, v7, ULID and short IDs only: there is no v1, v3, v5 or v6 here, and no namespace-based generation. It is a generator, not a parser, so you cannot paste an existing UUID in to have its version or timestamp decoded. Output is copied to the clipboard; there is no file download. The monotonic counter that keeps same-millisecond v7s and ULIDs in order is per-tab, so two tabs generating simultaneously can interleave — which is exactly how multiple servers behave in production, and why v7 ordering is approximate rather than a guarantee of true global sequence.

One last thing, said plainly: do not migrate a working schema to v7 because you read an article. Changing a primary key type means rewriting the table, rebuilding every index, updating every foreign key, and coordinating a deploy across everything that stores those IDs — for a benefit that is often invisible below tens of millions of rows. v7 is a good default for the next table you create. It is rarely a good reason to touch the one you already have. If you need related tools while you work, the hash generator and password generator run the same way — entirely in your browser.

Frequently asked questions

Should I use UUID v4 or UUID v7 for a database primary key?

For a new table, v7 is the better default. Its millisecond prefix means new rows append to the right edge of the B-tree index instead of scattering across it, which avoids the page splits and cache misses a random v4 key causes as the table grows. Choose v4 instead when the key is exposed publicly and the creation time of the record needs to stay private, since a v7 carries that timestamp in the open.

Is UUID v7 an official standard?

Yes. UUID v7 was standardised in RFC 9562, published in May 2024, which replaced the older RFC 4122 and also added versions 6 and 8. Before that it existed only as a draft, which is why some older libraries and databases do not have built-in support yet. A generated v7 is a normal 128-bit UUID, so any column or type that accepts a UUID accepts it.

Does a UUID v7 reveal when a record was created?

Yes, to the millisecond. The first 48 bits of a v7, and the first 10 characters of a ULID, are a plain Unix timestamp that anyone can read out of the ID. Inside your database that is useful. In a public URL or API response it publishes a fact about the record, such as a signup date or when an order was placed. If that matters, keep the v7 internal and give the record a separate v4 for public use.

Can two UUIDs ever be identical?

In theory yes, in practice no. A v4 has 122 random bits, and the birthday bound says you would need around 3.3 × 10^17 of them before there was even a 1% chance of a single duplicate — roughly a billion new UUIDs every second for a decade. Real collisions almost always come from a weak random source, such as a library built on Math.random(), rather than from exhausting the space.

Should I migrate my existing UUID v4 primary keys to v7?

Usually not. Changing a primary key type rewrites the table, rebuilds every index and touches every foreign key and every system that stores those IDs, for a gain that is often invisible below tens of millions of rows. Use v7 for new tables and leave a working schema alone unless you have measured a real index or write-throughput problem and traced it to key randomness.

Is a UUID safe to use as a session or password reset token?

A v4 from a cryptographic random source has 122 unpredictable bits, which is enough entropy for a bearer token, but entropy is only part of it: the token still needs a short expiry, single use, and storage as a hash rather than in plain text. Never use a v7 or ULID for this, because 48 bits of them are a predictable timestamp, and never use a UUID from a library that relies on Math.random().