Validating a UUID
Matching 32 hex digits proves the shape, not the standard. A value is an RFC 9562 identifier only if its version nibble is 1 to 8 and its variant nibble is 8, 9, a or b.
Most validation code stops at a regex, which accepts plenty of values no generator ever produced. This page separates the three questions people actually mean by "valid", and shows what the converter reads from each string.
0c5b2444-70a0-4932-980c-b4dc0d3f02b5outuuid · RFC 9562 (10xx) · version 4Canonical and standard
01890a5d-ac96-774b-bcce-b302099a8057outuuid · RFC 9562 (10xx) · version 7A v7 — same check, different layout
00000000-0000-0000-0000-000000000000outuuid · NCS (0xxx) — reserved, backward compatibility · no version · nil, palindromeWell formed, but neither version nor variant
deadbeefdeadbeefdeadbeefdeadbeefoutuuid · Microsoft (110x) — reserved, backward compatibility · no version · deadbeef, non-rfcHex, right length, and not an RFC 9562 identifier at all
On this page (7)
Three different questions#
| Question | What to check |
|---|---|
| Is it shaped like a UUID? | 32 hex digits, optionally grouped 8-4-4-4-12 |
| Is it an RFC 9562 identifier? | version nibble 1-8 and variant nibble 8, 9, a or b |
| Is it one of ours? | the version you generate, plus whatever your own layout requires |
Most code needs the first two. Rejecting a value because it is a v1 when you generate v7 is the third question, and it belongs in the domain layer, not in a parser.
RFC 9562 § 4.2 · Version FieldThe version field
RFC 9562 § 4.1 · Variant FieldThe variant field, and the patterns that are not this standard
The regex, and its limits#
// shape only
const SHAPE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
// RFC 9562: version 1-8, variant 10xx
const RFC = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
RFC.test("00000000-0000-0000-0000-000000000000"); // false: Nil has no version
RFC.test("ffffffff-ffff-ffff-ffff-ffffffffffff"); // false: Max has none eitherNote what the stricter pattern rejects: the Nil and Max UUIDs are defined by the standard and match neither. If your input may legitimately be a placeholder, accept those two explicitly rather than loosening the pattern.
Strings that look like UUIDs and are not#
- A ULID: 26 characters of Crockford base32, no hyphens — a different alphabet entirely.
- A hex digest cut to 32 characters: right length, arbitrary version nibble.
- A GUID read from a mixed-endian byte array: valid shape, wrong bytes, usually an impossible version.
- A base64 UUID at 22 characters: the same 16 bytes, a form no UUID regex will match.
Be strict on the way out, generous on the way in#
The converter accepts braces, the urn prefix, bare hex, capitals, quotes and surrounding whitespace, because that is what people paste out of logs and spreadsheets. Do the same at your edges: normalise first, validate the normalised form, store one spelling. Rejecting {F81D4FAE-…} because of its braces is a support ticket, not a security control.
Questions people actually ask#
What regex validates a UUID?
For the shape, 8-4-4-4-12 hex. For an RFC 9562 identifier, additionally require version 1-8 and a variant nibble of 8, 9, a or b. Remember that Nil and Max match neither.
Is 00000000-0000-0000-0000-000000000000 a valid UUID?
It is the Nil UUID, defined by the standard, but it carries no version and no variant, so a strict RFC pattern rejects it. Accept it explicitly if placeholders are allowed.
Should validation reject a v1 if we only generate v7?
That is a domain rule, not a format rule. Parse the identifier, then decide separately whether that version is acceptable in that field.
Related#
Browse the reference
Start here
Making and converting in bulk
Converting a UUID
Reading one
The versions, in order
In a database
ULID and the alternatives
Running the tool