UUID generator · Guide
GUID Generator JavaScript: crypto.randomUUID and Safe Fallbacks
JavaScript has had a built-in UUID generator since 2021, which makes most of the snippets still circulating on the web obsolete — and several of them were never safe to begin with. Here is the current answer, the fallback for the contexts that still need one, and the reasoning that tells you which to reach for.
Use crypto.randomUUID()
const id = crypto.randomUUID();
// '36b8f84d-df4e-4d49-b662-bcde71a8764f'That is the whole implementation. It returns a standards-compliant v4 UUID as a 36-character lowercase string, using the platform's cryptographically secure random source. There is no dependency to install and no snippet to maintain.
The format is fixed by RFC 9562: eight hex digits, then three groups of four, then twelve, with the version nibble always 4 and the variant bits set. That gives 122 random bits — the other six carry version and variant.
GUID and UUID are the same thing. GUID is Microsoft's name for the format, UUID is the RFC's, and both describe the same 128-bit value. Anything that accepts one accepts the other, so a question about a GUID generator in JavaScript is answered by crypto.randomUUID().
Where it is available
Support is now effectively universal, but two environment details catch people out often enough to be worth stating precisely.
Chrome / Edge 92+ Firefox 95+ Safari 15.4+
Node 19+ globalThis.crypto.randomUUID()
Node 14.17 - 18 require('crypto').randomUUID()
Deno, Bun built inIn browsers crypto.randomUUID only exists in a secure context. That means HTTPS, or localhost. On http://192.168.1.10:3000 — a phone testing against your dev machine over the LAN — it is undefined, while the same code on http://localhost:3000 works. Nearly every report of crypto.randomUUID is not a function in development traces back to this.
// Node 14.17-18: crypto is not global
import { randomUUID } from 'node:crypto';
const id = randomUUID();In Node before 19, crypto is not on the global object, so browser-shaped code fails on the server. If the same module runs in both places — SSR, an edge function, a shared utility — import from node:crypto or feature-detect rather than assuming a global.
Never generate UUIDs with Math.random
The widely copied snippet built on Math.random() produces UUIDs that look correct and are predictable.
// Do not use this
'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0;
return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
});Math.random() is not cryptographically secure and was never intended to be. Engines implement it with a fast PRNG — xorshift128+ in V8 — seeded from a small amount of state. Given enough observed output, the internal state can be recovered and both future and prior values reconstructed. The V8 team has said explicitly that it must not be used for anything security-related.
That is harmless for a React list key and dangerous for a session token, a password-reset link, an invitation code, or any identifier an attacker benefits from guessing. The failure is silent: the output looks exactly like a real UUID, so nothing in testing reveals the problem.
Since crypto.randomUUID() is shorter to write and available everywhere the snippet runs, there is no remaining reason to keep it in a codebase.
A fallback that is actually secure
When you must support a context without randomUUID — an insecure origin, or an older runtime — build on crypto.getRandomValues, which has near-universal support and is a CSPRNG.
function uuidv4() {
if (globalThis.crypto?.randomUUID) return crypto.randomUUID();
const b = crypto.getRandomValues(new Uint8Array(16));
b[6] = (b[6] & 0x0f) | 0x40; // version 4
b[8] = (b[8] & 0x3f) | 0x80; // variant RFC 9562
const h = [...b].map((x) => x.toString(16).padStart(2, '0'));
return `${h.slice(0, 4).join('')}-${h.slice(4, 6).join('')}-` +
`${h.slice(6, 8).join('')}-${h.slice(8, 10).join('')}-` +
`${h.slice(10).join('')}`;
}The two bit operations are not optional. They set the version nibble to 4 and the variant bits to the RFC value; skipping them produces a string that fails strict UUID validation in Postgres, in .NET and in most parsers, even though it looks fine.
padStart(2, '0') matters just as much. Without it, any byte below 0x10 renders as a single hex digit and the UUID comes out 35 characters long — an intermittent bug that appears in roughly one identifier in sixteen and is unpleasant to track down.
Validating a UUID
Validation is where a permissive regex quietly lets malformed values through. This one checks version and variant rather than only shape.
const UUID_RE =
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
UUID_RE.test('36b8f84d-df4e-4d49-b662-bcde71a8764f'); // true
UUID_RE.test('36b8f84d-df4e-0d49-b662-bcde71a8764f'); // false, version 0The third group's first character is the version and the fourth group's first character encodes the variant, which is why they are constrained rather than left as [0-9a-f]. The nil UUID 00000000-0000-0000-0000-000000000000 is valid per the RFC but fails this pattern, so accept it explicitly if your data uses it as a sentinel.
When v4 is the wrong choice
A v4 UUID is random, and randomness is exactly what hurts when the value becomes a database primary key. Each insert lands at an arbitrary point in the index, which fragments B-tree pages and degrades write throughput as the table grows.
UUID v7 solves this by putting a millisecond timestamp in the high bits, so values generated later sort after earlier ones while staying globally unique. Inserts append to the end of the index the way an auto-increment key does, and you keep the ability to generate IDs client-side.
import { v7 as uuidv7 } from 'uuid';
const id = uuidv7();
// '018f4a2b-7c00-7000-8000-...' — time-orderedThere is no built-in v7 yet, so this is one of the cases where the package earns its place. The trade-off is that a v7 UUID leaks its creation time — fine for internal keys, worth thinking about for anything public.
On collisions: v4 has 122 random bits, so generating a billion per second for a century leaves the chance of a duplicate negligible. Practical collisions come from a weak random source, not from the format — which is the whole argument against the Math.random version.
When the uuid package is still worth it
The uuid npm package earns its place when you need versions the platform does not provide: v1, v5 name-based UUIDs derived deterministically from a namespace, v7 time-ordered identifiers, or parsing and stringifying between byte arrays and text.
For plain random identifiers in application code, the built-in is smaller, faster and one less dependency to audit. Dropping it usually removes the package entirely — worth checking, because it still appears in many bundles purely for a v4 call the platform now handles.
Frequently asked questions
Why is crypto.randomUUID undefined?
Almost always because the page is not in a secure context. Browsers expose it over HTTPS and on localhost, but not on a plain-HTTP LAN address such as http://192.168.1.10:3000. In Node before version 19, crypto is not global — import randomUUID from node:crypto instead.
Is a GUID the same as a UUID?
Yes. GUID is Microsoft's name and UUID is the RFC's name for the same 128-bit format. They are interchangeable, so crypto.randomUUID() is the answer to both.
Can I use Math.random to generate a UUID?
Not for anything that matters. Math.random() is a fast non-cryptographic PRNG whose internal state can be recovered from observed output, making the values predictable. It is acceptable for a throwaway DOM key and unsafe for tokens, reset links or invitation codes. Use crypto.randomUUID() or crypto.getRandomValues().
Do I still need the uuid npm package?
Only for what the platform does not do: v1, v5 name-based UUIDs, v7 time-ordered IDs, or converting between byte arrays and strings. For plain v4 generation the built-in is faster and removes a dependency.
Should I use UUID v4 or v7 as a database primary key?
v7 for a primary key in a large table. Its leading timestamp makes values time-ordered, so inserts append to the index instead of scattering through it and fragmenting pages. Use v4 where unpredictability matters more than insert locality, and note that v7 reveals when the identifier was created.
How likely is a UUID collision?
With 122 random bits, negligible: generating a billion v4 UUIDs per second for a century still leaves a vanishingly small probability of a duplicate. Real-world collisions come from a weak random source such as Math.random, not from the format itself.
Ready to try it?
Open the free browser-based UUID generator and apply what you just read — no sign-up, runs locally.
Open the UUID generator tool