▸ Free tool
UUID Generator.
Crypto-random v4 UUIDs, generated by your browser's own CSPRNG. Set how many, generate, copy — nothing is sent anywhere.
▸ This makes v4, not v7
Great for test data, seeds, correlation IDs and idempotency keys. If these are going to be primary keys in a large table, generate v7 or ULID at the source instead — the reason is below.
What a v4 UUID actually is
A UUID is 128 bits, printed as 32 hex digits in an 8-4-4-4-12 grouping. In
version 4, six of those bits are not yours: four say "this is version 4"
and two mark the variant. The other 122 come straight out of a random
source. You can read both fields off the string without a parser: the
third group starts with the version digit, always
4 here, and the fourth group starts with
the variant digit, always 8,
9, a, or b.
This page calls crypto.randomUUID() and
falls back to crypto.getRandomValues()
with those six bits set by hand. No Math.random() anywhere, which turns out to be the whole ballgame.
The collision maths, in plain terms
122 random bits is about 5.3 × 10³⁶ values, but the number that matters is the birthday bound, which sits near the square root of that. Concretely: generate 103 trillion v4 UUIDs and your odds of a single duplicate anywhere in the pile are roughly one in a billion. You are not going to collide.
What you can break is the assumption underneath the maths: a real CSPRNG.
Duplicates in the wild come from
Math.random(), from a PRNG in a
process that forked after seeding, from images that boot with an identical
entropy pool, and from code that truncates a UUID to fit a legacy column.
Keep the unique constraint anyway — not as a retry mechanism, but as
documentation of intent.
The version decides your write pattern
The versions are not quality tiers, they are different trade-offs. v1 embeds a timestamp and a node ID that is traditionally the machine's MAC address — it leaks both, and because the timestamp fields sit out of order, it does not even sort by time. v4 is pure randomness. v7, standardised in RFC 9562 in 2024, leads with 48 bits of Unix milliseconds, big-endian, then randomness.
| v1 | v4 | v7 | |
|---|---|---|---|
| What the 128 bits hold | 60-bit timestamp + 14-bit clock sequence + 48-bit node ID | 122 random bits | 48-bit millisecond timestamp + up to 74 random bits |
| Sorts by creation time | |||
| Leaks something | MAC address and creation time | Creation time, by design | |
| Good primary key at scale | |||
| Where it is specified | RFC 4122, kept in RFC 9562 | RFC 4122, kept in RFC 9562 | RFC 9562 (2024) |
That ordering is not cosmetic — it is an index problem, and it is the production failure people actually hit. A primary key is a B-tree key. Time-ordered values append to the rightmost leaf page, which stays hot in the buffer pool. Random v4 values target a random page every time: the working set becomes the entire index, pages split down the middle instead of filling up, and the moment the index outgrows RAM your insert throughput falls off a cliff. MySQL with InnoDB suffers worst, because the table is the primary-key B-tree and every secondary index carries a full copy of that key.
This tool generates v4. For keys in a table that will get large, reach for
v7 or ULID where the row is created —
uuidv7() is built into PostgreSQL 18 and
there is a library for every language. ULID is the same idea (48 bits of milliseconds
plus 80 random) in a 26-character base32 encoding that sorts as text; v7 wins
on being a real UUID that drops into a uuid column.
Store 16 bytes, not 36 characters
A UUID is 128 bits. The canonical text form is 36 characters, so
char(36) spends 36 bytes to hold 16 bytes
of information. PostgreSQL has a native uuid type; MySQL does not, so use binary(16) with UUID_TO_BIN() and BIN_TO_UUID() at the edges; SQL Server has uniqueidentifier.
The 20 wasted bytes sound trivial until you remember that InnoDB copies the full primary key into every secondary index — five indexes, five more copies of the waste, in RAM as well as on disk.
When an auto-increment integer still wins
A bigint is 8 bytes, strictly monotonic,
readable in a log, and half the width of a UUID in every index and foreign key.
One database, one writer, IDs that never appear in a URL — that is the correct
choice, and a UUID there is pure tax.
UUIDs earn their place when you need the ID before the insert (client-side
creation, offline-first sync, idempotency keys), when you merge rows from
several sources, when you shard, or when the ID is public — /orders/1024 tells a competitor your order count and invites a stranger to try 1025. Most
teams land on both: a bigint primary key
for joins, plus an indexed UUID column for the outside world.
Questions, answered.
Are UUIDs guaranteed to be unique?
Not guaranteed — just overwhelmingly unlikely to collide. A v4 UUID carries 122 random bits, and the birthday bound puts you at roughly a one-in-a-billion chance of a single duplicate after generating 103 trillion of them. The real risk is never the maths, it is the random source: values drawn from Math.random(), from a PRNG seeded identically across forked workers, or from a library that truncates the UUID to fit a column will collide long before probability says they should.
Are the UUIDs from this generator cryptographically random?
Yes. The page calls crypto.randomUUID(), which the Web Crypto spec requires to be backed by a cryptographically secure random source, and falls back to crypto.getRandomValues() with the version and variant bits set by hand where randomUUID() is not available. Both need a secure context, so HTTPS or localhost. Nothing is generated on a server and nothing is sent anywhere — load the page once and it keeps working offline.
Does this tool generate UUID v7?
No. This generates v4 only, and I would rather say that than pretend otherwise. v7 puts a 48-bit millisecond timestamp at the front so the values sort by creation time, which is exactly what you want for database primary keys — so generate it where the row is created, not by pasting from a web page. PostgreSQL 18 ships a uuidv7() function, and every major language has a library.
Should I use a UUID as a database primary key?
It depends on the version and the write volume. Random v4 keys scatter inserts across the whole B-tree instead of appending to the rightmost page, which causes page splits, index fragmentation, and a sharp drop in insert throughput once the index stops fitting in memory. It hurts most on MySQL with InnoDB, where the table itself is the primary-key B-tree and every secondary index stores a full copy of that key. If you want a UUID primary key, use a time-ordered one: v7 or ULID.
How should I store a UUID in MySQL or PostgreSQL?
As 16 bytes, never as a 36-character string. PostgreSQL has a native uuid type that stores the raw 128 bits; MySQL has no UUID type, so use binary(16) with UUID_TO_BIN() and BIN_TO_UUID() to convert at the edges. A char(36) column wastes 20 bytes on every row, and in InnoDB that waste is repeated inside every secondary index, on top of slower string comparison under a collation.
Can I use a UUID as an API key or a password reset token?
A v4 UUID from a proper CSPRNG has 122 bits of entropy, so guessing one is not the problem. The format is. UUIDs get logged, pasted into URLs, and leaked through Referer headers, and nothing about the shape of a UUID tells the next developer that this particular one is a secret. Generate a purpose-made random token instead, keep it out of URLs, and store only its hash.
Keep going
Tool
Secret Generator
A UUID is an identifier, not a secret. When you need a token, pick the entropy and the alphabet on purpose.
Tool
SHA Hash Generator
The other half of ID hygiene: hash a token before you store it, and fingerprint content for cache keys.
Article
Stateless vs stateful apps
Client-generated IDs are what let a stateless service take the same retry twice and write the row once.
Article
Monolith vs microservices
The moment IDs have to be unique across services that do not share a database, a sequence stops being an option.
Keep reading
-
▸ Tool
Secret & API Key Generator
UUIDs are identifiers, not secrets. Use this when you need entropy.
-
▸ Tool
Shopify GID Decoder
The other ID format you end up staring at, with a type baked in.
-
▸ Post
/blog/laravel-multi-tenancy/
Where UUID vs auto-increment keys becomes a tenancy decision.
-
▸ Post
/blog/monolith-vs-microservices/
Globally unique IDs stop being optional once one database becomes several.