Bulk UUIDs in version 4 and version 7, generated from your browser's crypto source. Below: what the hex groups actually encode, when v7 beats v4, the collision math in real numbers, and how to generate and store UUIDs in the common languages and databases.
What the 36 characters mean
A UUID is 128 bits rendered as 32 hex characters in five dash-separated groups (8-4-4-4-12). The dashes carry no information; they are fixed formatting. Two small fields inside are pinned by RFC 9562, the 2024 revision that replaced RFC 4122 and added v7: the version nibble right after the second dash, and two variant bits at the start of the fourth group, which is why that group always begins with 8, 9, a or b.
| Version | Layout | Random bits | Property |
|---|---|---|---|
| v4 | random · version · random | 122 | unguessable, no structure, no order |
| v7 | 48-bit Unix ms timestamp · version · counter · random | 73 per millisecond | sorts by creation time, as string and as bytes |
The generator makes the structure visible: the version character renders amber, and in a v7 the twelve timestamp characters render green with the decoded UTC time underneath. Watching the green prefix stay identical across a fast batch and the tail churn is the whole v7 story in one glance.
v4 or v7, the short version
v7 for database primary keys and anything you sort or range-scan: the timestamp prefix means new rows land at the right edge of the B-tree instead of at 122 random bits of nowhere. v4 for anything security-adjacent or where the creation time is nobody's business: session identifiers, reset links, public IDs that should not reveal when an account was created. That is the two-sentence version; the index-fragmentation benchmarks, the PostgreSQL 18 story and the migration questions have their own write-up in UUID v4 vs v7: which one for your database keys?.
How to use this generator
Pick v4 or v7, set the count, click a row to copy it; copy all and the .txt download take the whole batch for seeding fixtures or importing into a database. Everything regenerates live when an option changes, and nothing leaves the tab: the values come from crypto.getRandomValues and the page keeps working offline.
The formatting flags cover the variants other systems expect: --uppercase and --braces produce the Windows registry GUID form, --no-dashes the compact 32-character form some APIs and MySQL BINARY(16) workflows want. One detail most generators skip: our v7 batches use the RFC 9562 counter method, seeding twelve counter bits fresh each millisecond and incrementing within it. A thousand UUIDs generated in the same millisecond still come out strictly ordered, which is exactly the property you wanted from v7 in the first place, and it is why the collision stat for v7 reads none rather than a probability.
v4 or v7, the short version
v7 for database primary keys and anything you sort or range-scan: the timestamp prefix means new rows land at the right edge of the B-tree instead of at 122 random bits of nowhere. v4 for anything security-adjacent or where the creation time is nobody's business: session identifiers, reset links, public IDs that should not reveal when an account was created. That is the two-sentence version; the index-fragmentation benchmarks, the PostgreSQL 18 story and the migration questions have their own write-up in UUID v4 vs v7: which one for your database keys?.
The collision math, in numbers you can check
The batch stat above shows the birthday approximation p ≈ n(n−1)/2 ÷ 2¹²², and the honest headline is that every realistic number rounds to zero. Ten thousand v4 UUIDs: about 1 in 10³⁰. A billion: 1 in 10²⁰. To reach a coin-flip chance of one collision you need about 2.7 × 10¹⁸ UUIDs, a billion per second for 86 years, stored in 43 exabytes.
When collisions do happen in practice, the cause is never the birthday bound. It is a generator that was not random: Math.random seeded per-worker, a forked VM resuming the same PRNG state twice, a mocked clock in tests feeding v7 the same millisecond and a library without the counter, or fixture data copy-pasted between environments. Which is the actual reason to care what a UUID page runs on. This one draws every byte from the OS CSPRNG and does the version and variant masking on top; there is no seed to clone.
Generating UUIDs in code
| Environment | v4 | v7 |
|---|---|---|
| JavaScript / Node.js | crypto.randomUUID() | uuid package ≥ 10 (uuidv7()) |
| Python | uuid.uuid4() | uuid.uuid7() from 3.14; before that the uuid6 package |
| PostgreSQL | gen_random_uuid() | uuidv7() built in from 18 |
| MySQL | app-side (built-in UUID() is v1) | app-side |
| Java | UUID.randomUUID() | java-uuid-generator |
| C# / .NET | Guid.NewGuid() | Guid.CreateVersion7() from .NET 9 |
| Go | google/uuid New() | NewV7() |
The one to double-check is MySQL: its UUID() function returns a version 1 value with the server's MAC address baked in, which is neither of the two versions you probably want. Generate in the application instead. And in shell scripts, uuidgen produces v4 on both macOS and Linux; the lowercase/uppercase default differs between the two, which matters the moment a script compares them as strings.
Storing UUIDs properly
A UUID is 16 bytes; the 36-character string is a display format. Native column types (uuid in PostgreSQL, UNIQUEIDENTIFIER in SQL Server, BINARY(16) in MySQL) store the bytes, compare without collation surprises, and keep every index that touches the column 20 bytes per entry smaller than a CHAR(36). With v7 keys the byte order is also the time order, so a plain index range scan walks creation time with no extra column.
Convert at the boundary, once. Parse incoming strings into the native type in the API layer, emit lower case in JSON, and never let the string form leak into joins. Mixed-case string joins across two ORMs are the classic way two tables full of identical UUIDs manage not to match.
UUID questions
How many UUIDs can you generate before a collision becomes likely?
For random v4 UUIDs the birthday math says you need about 2.7 quintillion (2.7 × 10^18) before the odds of a single collision reach 50%, which is one billion UUIDs per second for roughly 86 years. A more practical anchor: after 103 trillion UUIDs the chance that any two match is still one in a million. Collisions in the wild come from broken generators, not from the math: seeding a non-cryptographic PRNG twice with the same value, cloning a VM mid-state, or copy-pasting fixture data. If your generator uses the operating system CSPRNG, treat v4 collisions as impossible.
Is crypto.randomUUID() safe to use in JavaScript?
Yes. crypto.randomUUID() produces a v4 UUID from the browser or Node.js CSPRNG, and it has been in every major browser since 2021 (Chrome 92, Firefox 95, Safari 15.4) and in Node.js since 14.17. Two caveats: in browsers it only exists in secure contexts, so it is undefined on plain http:// pages other than localhost, and it only makes v4. For v7 you need a library such as uuid (v10+ has uuidv7) or the uuidv7 package, since no built-in browser API generates v7 yet.
Are UUIDs case-sensitive?
No. 916F1BDA and 916f1bda are the same 128 bits. Only naive string comparison cares, so normalise to lower case at every boundary.
What is the difference between a GUID and a UUID?
Nothing, in any way that matters today: GUID is Microsoft’s name for the same 128-bit value, from the COM era of the early 1990s. The formats are byte-identical and RFC-compliant tooling reads both. The visible differences are conventions, not structure: Windows tooling tends to print GUIDs in upper case and often wraps them in braces like {916F1BDA-...}, which this generator reproduces with --uppercase and --braces. One historical footnote: .NET’s Guid.ToByteArray() emits the first three fields little-endian, so raw byte dumps from .NET can look scrambled next to the string form.
How do I validate a UUID with a regex?
The loose check for any canonical UUID is /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i. To pin the version and variant, constrain the two positions that carry them: /^[0-9a-f]{8}-[0-9a-f]{4}-[47][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i accepts v4 and v7 with the standard variant. Decide deliberately whether the nil UUID (all zeros) should pass: it matches the loose pattern but no versioned one, and it usually signals a bug upstream rather than a real identifier.
What are the nil UUID and the max UUID?
All zeros and all fs, both RFC 9562 sentinels. A nil UUID in real data is almost always an accident.
Can I use a UUID v4 as a session token or API key?
The entropy is fine: 122 random bits from a CSPRNG is more than the 128 bits OWASP asks of session identifiers. The risks are around the edges. The UUID must actually come from a cryptographic source (some older libraries and copy-pasted snippets use Math.random), it must not be a v1 or v7, which embed a timestamp and, for v1, a MAC address, and it gains nothing from its format: the dashes cost 4 characters of transport for zero entropy. For new token designs, 32 random bytes hex- or base64url-encoded are simpler and stronger; for an existing system handing out v4s, there is no urgent problem.
Why does every UUID have a 4 in the same position?
The character after the second dash is the version field, so every v4 UUID shows a literal 4 there and every v7 shows a 7. The first character of the following group encodes the variant and is always 8, 9, a or b for standard UUIDs. That is why a v4 has 122 random bits rather than 128: six bits are pinned by these two fields. This generator tints the version character amber so you can see it against the random remainder.
Should I store UUIDs as text or as binary in a database?
Use the native 16-byte type where one exists: uuid in PostgreSQL, UNIQUEIDENTIFIER in SQL Server, BINARY(16) in MySQL and MariaDB (MySQL 8 has UUID_TO_BIN/BIN_TO_UUID to convert at the boundary). Storing the 36-character string in a CHAR/VARCHAR column costs 20 extra bytes per row, repeats that cost in every index that includes the column, and makes comparisons collation-dependent, which is how the case-sensitivity bugs get in. The string form is for humans and JSON; the moment it hits storage it should be bytes.