cheat sheet
uuid
Package-level reference for uuid on npm — v4 random, v5 namespace, v7 sortable, ESM-only since v9, and namespace pattern.
uuid
What it is
uuid is the canonical npm package for generating RFC 4122 (and now RFC 9562) UUIDs. It implements every version that's been standardized: v1 (timestamp + MAC), v3 (namespace + MD5), v4 (random — the most common), v5 (namespace + SHA-1), v6 (reordered v1 — sortable), v7 (Unix-timestamp + random — sortable, new in 2024-2025), and v8 (custom-domain).
It's the de-facto choice over hand-rolled crypto.randomUUID() for two reasons: (1) namespace-based deterministic UUIDs (v3, v5) aren't possible from the built-in API, and (2) v7 (sortable, monotonic, better B-tree locality) is increasingly the recommended choice for new databases — Node's built-in crypto.randomUUID() only emits v4.
Install
# npm / pnpm / yarn / bun
npm install uuid
pnpm add uuid
yarn add uuid
bun add uuid
Output: runtime dep. Pure JS; ~1 KB gzipped after tree-shaking individual versions.
# TypeScript declarations are now bundled in v9+ — no separate @types/uuid needed
# (still install for legacy v8 codebases)
npm install --save-dev @types/uuid # only for v8 and below
Output: TS support — bundled since v9.
# CLI tool — generate a UUID from the shell
npx uuid # v4 by default
npx uuid v7 # v7 — sortable
npx uuid v5 ns:URL https://example.com
Output: quick interactive use. The uuid binary is installed by the package and works under npx.
Versioning & Node support
- Current major line is
11.x(stable; cleanups, additional v7 hardening, full RFC 9562 compliance). The10.xline introduced the new v7 monotonic counter. The9.xline is the ESM-only transition. v9was a breaking change: ESM-only, drops CJSrequire("uuid"). Many codebases stuck onv8because of CJS interop.- Pure JS; runs on any modern runtime — Node 14+ (for
v11, prefers Node 18+ for crypto.webcrypto), Bun, Deno, Cloudflare Workers, browsers. - ESM only since v9. No
require("uuid"); you must useimport { v4 } from "uuid". CJS interop via dynamicimport()works. - Always a runtime dependency — you call it at runtime to produce IDs.
- Types are bundled in v9+.
Package metadata
- Maintainer: Christoph Tavan (
@ctavan) + the uuidjs org - Project home: github.com/uuidjs/uuid
- Docs: github.com/uuidjs/uuid#readme
- npm: npmjs.com/package/uuid
- License: MIT
- First released: 2010 (as
node-uuid); renamed touuidin 2017 - Downloads: ~100 million per week — top-10 package on npm
Peer dependencies & extras
Zero runtime dependencies. Internally uses the platform crypto (WebCrypto in browsers, node:crypto in Node, all bundlers patch this correctly).
| Package | Purpose |
|---|---|
@types/uuid | TS declarations (only needed for v8 and below — v9+ bundles types) |
short-uuid | Encodes UUIDs into shorter URL-safe representations (base57, etc.) |
uuid-by-string | Wrapper for deterministic v3/v5 from a single string input |
nanoid | Not a uuid but the closest alternative — see Alternatives |
ulid | Another sortable-ID alternative — see Alternatives |
Alternatives
| Library | Trade-off |
|---|---|
crypto.randomUUID() (built-in) | Free, zero-dep, fast, in every modern runtime since Node 14.17 / all browsers. Only emits v4. Pick when you only need random UUIDs. |
nanoid | Shorter (21-char vs 36-char), URL-safe, ~120 bits entropy. Not RFC-compliant. Pick when bytes-on-wire matter and you don't need UUID interop. |
ulid | Sortable, 26-char Crockford base32, 128 bits. Pre-dates UUID v7. Pick if v7 isn't yet supported by your DB driver and you need sortable IDs. |
cuid2 | Modern collision-resistant ID; designed for distributed systems. Not RFC-compliant. Pick when ID-shape is yours alone (no third-party UUID parsers). |
hyperid | Extremely fast (>5M ids/sec); not RFC-compliant. Pick for very hot ID-generation paths. |
@paralleldrive/cuid2 | Reference impl of cuid2. Same trade-off as cuid2. |
Common gotchas
uuidis ESM-only since v9.require("uuid")throwsERR_REQUIRE_ESM. Either upgrade your project to ESM, use dynamicimport(), or pinuuid@^8.- v4 vs
crypto.randomUUID()— same output, different ergonomics.crypto.randomUUID()is faster (native), zero-dep, and produces an identical v4 string. Use it unless you need v5/v7 too. - v5 needs a namespace.
v5(value, namespace)— namespace must itself be a valid UUID string or aUint8Array. Use one ofuuid.NIL,uuid.v5.URL,uuid.v5.DNS, or your own org-specific UUID constant. - v7 monotonicity is per-process. Two processes generating v7 in the same millisecond can collide on the timestamp portion (low order bits still random). Don't expect strict global monotonicity across machines.
- TypeScript:
parsereturnsUint8Array, not string.uuid.parse("...")anduuid.stringify(...)round-trip between hex string and binary form. Useful for storing in a 16-byte column. validateaccepts ANY UUID version.validate(s)returns true for v1-v8 (and even the NIL UUID). Useversion(s)to discriminate.
Real-world recipes
v4 — random UUID (the 99% case)
import { v4 as uuidv4 } from "uuid";
const id = uuidv4();
// "6f9619ff-8b86-d011-b42d-00c04fc964ff"
// Or use the built-in if you have Node 14.17+ / modern browser
const id2 = crypto.randomUUID();
// Identical format
Output: 36-char hyphenated lowercase. Use crypto.randomUUID() if you don't need any other version of uuid.
v5 — deterministic namespace UUID
import { v5 as uuidv5 } from "uuid";
// Built-in namespaces
const URL_NS = uuidv5.URL; // "6ba7b811-9dad-11d1-80b4-00c04fd430c8"
const DNS_NS = uuidv5.DNS;
const userUrlId = uuidv5("https://example.com/users/42", URL_NS);
// always the same UUID for the same URL — deterministic
// Custom org-wide namespace
const MY_ORG_NS = "f47ac10b-58cc-4372-a567-0e02b2c3d479";
const externalId = uuidv5("customer-abc-123", MY_ORG_NS);
Output:
userUrlId = "a6cc5730-2261-55e2-9ad8-d3c8a8fdfa55"
externalId = "d4a8e2b1-7a0f-5e3d-8f4a-..." // always the same for "customer-abc-123" in this namespace
v5 is the go-to for "external IDs" — map a third-party identifier (Stripe customer ID, email, account number) to a deterministic UUID so re-importing the same row produces the same UUID.
v7 — sortable timestamp-based UUID (the new standard)
import { v7 as uuidv7 } from "uuid";
const id1 = uuidv7();
await sleep(2);
const id2 = uuidv7();
await sleep(2);
const id3 = uuidv7();
console.log([id1, id2, id3]);
console.log([id1, id2, id3].sort()); // same order — v7 sorts lexicographically by creation time
Output:
[
"018f5e2a-7c00-7c1d-8000-1b2c3d4e5f6a",
"018f5e2a-7c02-7c1d-8000-2b2c3d4e5f6a",
"018f5e2a-7c04-7c1d-8000-3b2c3d4e5f6a"
]
The first 48 bits are a Unix-ms timestamp; the next 12 bits are version/variant; the remaining 62 bits are random. Sorting v7 UUIDs gives chronological order — better B-tree locality than v4 in databases.
Use v7 instead of v4 for new primary keys in 2025+. Postgres index-locality on v7 is ~10× better than v4.
Namespace constant pattern
// src/lib/uuid-namespaces.ts — define once per project
import { v5 } from "uuid";
// Generate once with `uuidv4()` and check in — this is your org's root namespace
export const ORG_NS = "f47ac10b-58cc-4372-a567-0e02b2c3d479";
// Sub-namespaces, derived deterministically — no need to check in extras
export const USER_NS = v5("user", ORG_NS);
export const ORDER_NS = v5("order", ORG_NS);
export const FILE_NS = v5("file", ORG_NS);
// Usage
import { USER_NS } from "./uuid-namespaces";
import { v5 } from "uuid";
export function userIdFromEmail(email: string): string {
return v5(email.toLowerCase().trim(), USER_NS);
}
Output: clean separation of namespaces with one hard-coded root UUID. Deterministic — same email → same UUID, forever.
Binary storage round-trip
import { v4, parse, stringify } from "uuid";
const id = v4();
const bytes = parse(id); // Uint8Array(16)
const restored = stringify(bytes); // back to canonical string
// Store as bytea/blob in DB — 16 bytes vs 36 chars
console.log(bytes.length, id.length);
Output:
16 36
Use parse/stringify to halve UUID storage cost when your DB has a binary column type (Postgres uuid, MySQL BINARY(16)).
Validate + discriminate version
import { validate, version } from "uuid";
const ids = ["6f9619ff-8b86-d011-b42d-00c04fc964ff", "not-a-uuid"];
for (const id of ids) {
if (!validate(id)) {
console.log(`${id}: invalid`);
continue;
}
console.log(`${id}: v${version(id)}`);
}
Output:
6f9619ff-8b86-d011-b42d-00c04fc964ff: v4
not-a-uuid: invalid
validate is loose (accepts all versions). version extracts the version digit — useful when migrating from v4 to v7 and you want to find the legacy rows.
Production deployment
Use v7 for new primary keys
| ID type | Use case |
|---|---|
| v4 | Random tokens, session IDs, request IDs, anywhere sort order doesn't matter |
| v5 | Deterministic IDs from external identifiers (email, external customer ID) |
| v7 | Database primary keys; anywhere chronological sort + global uniqueness matters |
| v1, v6 | Legacy interop only; v1 leaks MAC address |
Bundle size on the browser
The full uuid package tree-shakes per-version. Importing only v4:
import { v4 } from "uuid"; // ~1 KB gzipped
vs
import * as uuid from "uuid"; // ~3 KB gzipped (all versions)
For a v4-only client bundle, prefer crypto.randomUUID() — zero KB and faster.
Edge runtime compatibility
uuid uses WebCrypto's getRandomValues — works on Cloudflare Workers, Vercel Edge, Deno, Bun, browsers, Node 18+. No platform-specific code.
Performance tuning
Throughput numbers (approximate, on M1 Pro, 2026)
| Function | ops/sec |
|---|---|
crypto.randomUUID() (native Node 21) | ~6M |
uuid.v4() | ~2M |
uuid.v7() | ~1.5M |
uuid.v5(str, ns) | ~500k |
nanoid() | ~3M |
For hot paths generating millions of IDs, use crypto.randomUUID(). For v5 / v7 / v3 the uuid package has no built-in equivalent.
v7 monotonic counter
In v10+ uuid uses a monotonic counter that increments within the same millisecond — two v7() calls in the same ms always produce sortable distinct UUIDs (in the same process). Across processes, the random suffix usually avoids collision; for paranoid hot-write systems, layer per-process namespacing on top.
Avoid validate in hot paths
validate(s) runs a regex — ~5-10× slower than just trying to use the ID. For trusted internal flows skip the validation; for untrusted input keep it.
Version migration guide
v8 → v9 — CJS → ESM
This is the migration most teams confront. v8 (June 2022) is CJS; v9+ (July 2023) is ESM-only.
// v8 (CJS)
const { v4: uuidv4 } = require("uuid");
const id = uuidv4();
// v9+ (ESM)
import { v4 as uuidv4 } from "uuid";
const id = uuidv4();
// v9+ in a CJS file — dynamic import
const { v4: uuidv4 } = await import("uuid");
If you can't migrate to ESM:
// package.json — pin to v8
"dependencies": { "uuid": "^8.3.2" }
This works indefinitely; v8 is feature-complete and gets security backports.
v9 → v10 — v7 monotonic counter
v10 (mid-2024) added the per-process monotonic counter for v7. The output format is unchanged; you just get strict ordering within a process even at sub-millisecond intervals.
v10 → v11 — RFC 9562 compliance
v11 hardened v7 and v8 against the final RFC 9562 text (published May 2024). No code changes needed — same exports, slightly different bit-packing internally.
Type-package deprecation
The community @types/uuid package is deprecated as of v9 — uuid now bundles its own types. If you upgrade past v9, uninstall @types/uuid:
npm uninstall @types/uuid
Output:
removed 1 package in 1s
Leaving both installed causes a duplicate-declaration error.
Security considerations
- v4 randomness depends on the underlying crypto source.
uuiduses WebCrypto'sgetRandomValues— fine on all modern platforms. Don't substituteMath.random()based libraries. - v1 leaks MAC address. RFC 4122 v1 embeds the node identifier (often MAC). Avoid for any public ID.
- v7 timestamps leak creation time. A v7 UUID encodes its creation millisecond in plaintext bytes. If creation time is sensitive (e.g. shows a user signed up at 2 AM), don't use v7 publicly. Use v4 or hash the v7.
- v5 with predictable namespace + value = predictable UUID. If both the namespace UUID and the input are known to an attacker, they can compute the result. Use v5 only when determinism is the goal; for opaque tokens use v4 + entropy.
uuidis not cryptographically random for short prefixes. Don't use the first 8 chars of a v4 as a session token — only 32 bits of entropy. Use the full 36-char string or 128 bits.- No CVEs of note. uuid has a clean security history; the only common bug-class is upstream
Math.random()substitution by mistake (don't fork or copy-paste old impls).
Testing & CI integration
import { describe, it, expect } from "vitest";
import { v4, v5, v7, validate, version } from "uuid";
describe("v4", () => {
it("produces valid UUIDs", () => {
const id = v4();
expect(validate(id)).toBe(true);
expect(version(id)).toBe(4);
});
});
describe("v5", () => {
it("is deterministic for same namespace + value", () => {
const NS = "f47ac10b-58cc-4372-a567-0e02b2c3d479";
expect(v5("alice@example.com", NS)).toBe(v5("alice@example.com", NS));
});
});
describe("v7", () => {
it("sorts chronologically", async () => {
const ids: string[] = [];
for (let i = 0; i < 5; i++) {
ids.push(v7());
await new Promise((r) => setTimeout(r, 1));
}
expect([...ids].sort()).toEqual(ids);
});
});
Output:
PASS v4 — produces valid UUIDs
PASS v5 — is deterministic for same namespace + value
PASS v7 — sorts chronologically
For deterministic-time tests, mock Date.now() so v7 timestamps are stable.
Ecosystem integrations
| Tool | Integration |
|---|---|
prisma | @default(uuid()) (Prisma uses its own v4 generator) or @default(dbgenerated("gen_random_uuid()")) on Postgres |
mongoose | default: () => uuidv4() on a String field |
drizzle-orm | Postgres uuid() column or text("id").$default(() => v7()) |
typeorm | @PrimaryGeneratedColumn("uuid") or supply manually |
express / fastify | Request-ID middleware (req.id = v4()) — wire to logger |
pino | Pino's genReqId accepts a uuid generator |
next.js | Server-side; crypto.randomUUID() works in middleware/edge too |
Troubleshooting common errors
Error [ERR_REQUIRE_ESM]: require() of ES Module ...— uuid v9+ is ESM-only. Either upgrade to ESM, use dynamicimport(), or pinuuid@^8.Cannot find module 'uuid'(TypeScript) — install@types/uuidfor v8 and below; v9+ bundles types.Subsequent property declarations must have the same type— bothuuidv9+ types and@types/uuidinstalled. Uninstall@types/uuid.- v5 returns the same UUID for different inputs — usually a namespace bug (you passed a string namespace, not a UUID-format namespace). Validate the namespace with
validate(NS). crypto is not definedin a worker — WebCrypto missing. Polyfill or upgrade runtime (Node 18+, modern browsers, Workers all have it built-in).- v7 IDs not monotonic across server restarts — that's correct. The monotonic counter is per-process. For cross-process monotonicity, use a database sequence or pull from a centralised ID service.
When NOT to use this
- You only need v4. Use
crypto.randomUUID()— built-in, faster, zero-dep, same output. - You need short URL-friendly IDs. Use
nanoid(21 chars vs 36; URL-safe by default). - You need IDs unique across distributed writers with strict ordering. Use a Snowflake-style generator (
flake-idgen, etc.) — coordinated; doesn't rely on randomness for uniqueness. - Your database has a native UUID generator. Postgres
gen_random_uuid(), MySQLUUID(), MongoDBObjectId. Generate at the database — one less moving part. - You're storing in 16 bytes anyway. Postgres v7 generators exist as extensions; same wire format, generated at the DB.
See also
- JavaScript: node runtime —
crypto.randomUUID()and WebCrypto - Concept: JSON — UUIDs in JSON bodies, parsing client tokens
- Concept: API — request-ID and idempotency-key patterns