UUID versions: a complete reference
On this page
UUIDs come in eight numbered versions plus the nil and max sentinels. Each is a 128-bit value with the same external shape, but the bytes are derived differently. This page is the side-by-side reference for all of them.
Quick comparison
| Version | What it encodes | Sortable? | Cryptographic? | Use today? |
|---|---|---|---|---|
| 1 | 60-bit timestamp (1582 epoch, 100ns ticks) + 14-bit clock seq + 48-bit node ID | no | no | legacy only |
| 2 | DCE Security UID (UID/GID + node + timestamp) | no | no | almost never |
| 3 | MD5(namespace + name) | n/a | no | use v5 instead |
| 4 | 122 random bits | no | yes (CSPRNG) | default for one-off IDs |
| 5 | SHA-1(namespace + name) | n/a | no (deterministic) | for derived IDs |
| 6 | v1 with timestamp bits reordered | yes | no | use v7 instead |
| 7 | 48-bit Unix-ms timestamp + 74 random bits | yes | yes (CSPRNG) | default for primary keys |
| 8 | Custom layout — vendor defined | varies | varies | rarely needed |
| nil | All zeros | n/a | n/a | sentinel |
| max | All ones (RFC 9562) | n/a | n/a | sentinel |
The two recommendations for new code:
- v4 when you need a one-off random ID and don’t care about ordering — the default unless you have a specific reason.
- v7 when the ID will be a database primary key, log line, or anything that benefits from time-ordered insertion and lexicographic sort.
Bit layouts
v1: ┌──────────────┬──────────┬──────────┬─────────┬─────────────┐
│ time_low(32) │ tm_mid(16)│ ver|tm_hi│ var|seq │ node(48) │
└──────────────┴──────────┴──────────┴─────────┴─────────────┘
v3/v5: ┌──────────────────────────────────────────────────────────┐
│ MD5/SHA-1(namespace || name) → 128 bits, ver/var bits set │
└──────────────────────────────────────────────────────────┘
v4: ┌──────────────────────────────────────────────────────────┐
│ 122 random bits, 4-bit version, 2-bit variant │
└──────────────────────────────────────────────────────────┘
v6: ┌─────────────────────────────────────────────────────────────┐
│ tm_hi(48) │ tm_mid(12) │ ver │ tm_lo(12) │ var │ rand(62) │
└─────────────────────────────────────────────────────────────┘
v7: ┌─────────────────────────────────────────────────────────────┐
│ unix_ms(48) │ ver │ rand_a(12) │ var │ rand_b(62) │
└─────────────────────────────────────────────────────────────┘
v1 — timestamp + node
The original UUID. Encodes when and where: 100-nanosecond ticks since 1582-10-15 plus the machine’s MAC address (or a random replacement).
Pros: every value is unique without coordination if node IDs are unique. Cons: lexicographic sort doesn’t match chronological — the timestamp bytes are reordered. If MAC is real, leaks identity.
v2 — DCE Security
A rare beast. Encodes a POSIX UID/GID into the timestamp area. Used by Microsoft DCE / DCOM in the early ’90s. Almost no library generates them today, and almost no system requires them. Don’t use unless you have a specific DCE compatibility requirement.
v3 — MD5 namespace
Hash a UUID namespace + a name string with MD5, set version = 3. Same input always produces the same UUID.
Pros: deterministic — same input → same UUID, no storage of the mapping needed. Cons: MD5 has known collisions for adversarial inputs. Use v5 instead for new code.
v4 — random
122 bits of cryptographic randomness. The default unless you have a specific reason to choose otherwise.
Pros: trivially generated, no coordination, astronomically unlikely to collide. Cons: random insertion fragments B-tree primary-key indexes. For DB keys, prefer v7.
v5 — SHA-1 namespace
Same idea as v3, but with SHA-1 instead of MD5. Faster than v3 on modern CPUs and slightly better collision properties — though still not cryptographically secure for adversarial inputs.
When to use: stable identifiers derived from a stable input (lowercase email, normalized URL, etc.). Two services can independently arrive at the same UUID without coordinating.
v6 — reordered v1
Standardized in RFC 9562. Takes v1’s timestamp bits and reorders them to make lexicographic sort match chronological. Solves v1’s biggest practical issue.
Use only for migrating from v1. For new code, v7 is simpler and avoids v1’s node-ID legacy entirely.
v7 — Unix-ms timestamp
The modern default for time-ordered IDs. 48-bit Unix millisecond timestamp at the start (in natural byte order), 74 random bits after the version/variant.
Pros: lexicographic sort = chronological. Excellent as DB primary key — see UUID as primary key. RFC standard, broad support arriving in language standard libraries (Python 3.13, .NET 9, Postgres 18).
Cons: timestamp is visible in the ID. If the creation time is sensitive, use v4.
v8 — custom
A “do whatever you want” version. RFC 9562 defines only the version/variant bits; the other 122 bits are vendor-defined. Useful for embedding application-specific data (sharding keys, tenant IDs, hash prefixes) while remaining a valid UUID.
Use only when you’ve designed a specific layout and have a reason to encode it as a UUID rather than a separate field. v8 isn’t a casual choice — most code that processes UUIDs assumes one of v1–v7’s layouts.
Nil and max
nil: 00000000-0000-0000-0000-000000000000
max: ffffffff-ffff-ffff-ffff-ffffffffffff
Both are RFC-reserved sentinels. No legitimate generator produces them. Useful as “not yet assigned” markers in databases or “upper bound” sentinels in range queries.
Decision matrix
What do you need?
├── A unique ID, nothing else → v4 (random)
├── A unique ID for a database primary key → v7 (time-ordered)
├── A reproducible ID from stable input → v5 (SHA-1 namespace)
├── A timestamp encoded inline → v7 (forward) or v1 (legacy)
├── A reserved "no value" marker → nil
├── Custom application-defined layout → v8 (rarely)
└── Anything else → v4 unless you can name the constraint
Code: generate every version
JavaScript (uuid npm)
import { v1, v3, v4, v5, v7, NIL } from "uuid";
v1(); // time-based
v3("name", v3.URL); // MD5 namespace
v4(); // random
v5("name", v5.URL); // SHA-1 namespace
v7(); // Unix-ms timestamp
NIL; // "00000000-..."
Python
import uuid
uuid.uuid1() # time-based
uuid.uuid3(uuid.NAMESPACE_URL, "name") # MD5 namespace
uuid.uuid4() # random
uuid.uuid5(uuid.NAMESPACE_URL, "name") # SHA-1 namespace
uuid.uuid7() # Python 3.13+
uuid.UUID(int=0) # nil
Go (google/uuid)
v1, _ := uuid.NewUUID() // time-based
v3 := uuid.NewMD5(uuid.NameSpaceURL, []byte("name"))
v4 := uuid.New() // random
v5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("name"))
v7, _ := uuid.NewV7()
nil := uuid.Nil
Versions are not interchangeable
Two UUIDs of different versions encoding the same input are different values. v3(“example”, URL_NS) ≠ v5(“example”, URL_NS). Don’t mix versions in a single column unless you have a reason — pick one per use case.
The version is detectable from the value itself (the 13th hex character is the version number), so a defensive check is easy:
import { version } from "uuid";
version("01928a47-3b30-7c5e-9d1a-f0b8c4a7e923"); // 7
Try the tools
- UUID generator — pick any version, copy the output
- UUID validator — paste a UUID, identify the version, decode the timestamp
- UUID v7 generator — focused page for the modern default
- UUID as primary key — when v4 vs v7 matters most