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