Developer Lab · TypeScript
Generate UUID in TypeScript
To generate a UUID in TypeScript, call crypto.randomUUID(). It returns an RFC 4122 version 4 UUID backed by the OS CSPRNG, typed as a template literal string. Layer on branded types to prevent mixing entity IDs at compile time, and use Zod to validate UUIDs from HTTP requests at runtime. For time-ordered v7 or deterministic v5 IDs, install the uuid package.
Quick Answer
Production ready// Branded UUID - compile-time safety, zero runtime cost
type Brand<T, B> = T & { readonly _brand: B };
type UUID = Brand<`${string}-${string}-${string}-${string}-${string}`, 'UUID'>;
function generateUUID(): UUID {
return crypto.randomUUID() as UUID;
}
const id: UUID = generateUUID();
// → "550e8400-e29b-41d4-a716-446655440000"
Step-by-Step Guide
Follow these four steps to generate, type, and safely validate UUIDs in any TypeScript project.
-
1
Configure your TypeScript environment
Browser projects need
"DOM"and"ES2021"(or later) incompilerOptions.lib. Node projects need@types/node. Without the correct lib,crypto.randomUUID()may be untyped or missing.tsconfig.json{ "compilerOptions": { "target": "ES2022", "lib": ["ES2022", "DOM"], "module": "ESNext", "moduleResolution": "bundler", "strict": true } } -
2
Generate a typed UUID v4
Call
crypto.randomUUID()and cast to your brandedUUIDtype. In Node.js 14.17–18, import fromnode:cryptoinstead.typescripttype UUID = string & { readonly _brand: 'UUID' }; // Browser / Node 19+ const id: UUID = crypto.randomUUID() as UUID; // Node 14.17–18 import { randomUUID } from 'node:crypto'; const serverId: UUID = randomUUID() as UUID; -
3
Create entity-specific branded types
Define
UserId,OrderId, and other domain IDs as distinct branded types. TypeScript will catch passing the wrong ID at compile time.typescripttype UserId = UUID & { readonly _brand: 'UserId' }; type OrderId = UUID & { readonly _brand: 'OrderId' }; function createUserId(): UserId { return crypto.randomUUID() as UserId; } function createOrderId(): OrderId { return crypto.randomUUID() as OrderId; } function getOrder(id: OrderId): Promise<Order> { /* ... */ } const userId = createUserId(); const orderId = createOrderId(); getOrder(orderId); // ✓ OK // getOrder(userId); // ✗ compile-time error -
4
Validate external UUIDs at runtime
Types are erased at runtime. Always validate UUIDs from HTTP requests, query params, and user input with a type guard or Zod schema.
typescriptimport { z } from 'zod'; // Zod 3 const UUIDSchema = z.string().uuid(); // Zod 4: const UUIDSchema = z.uuid(); function parseUserId(input: unknown): UserId { const id = UUIDSchema.parse(input); return id as UserId; }
Quick Reference
Choose the right approach for generation, typing, and validation.
| Approach | Version | Type safe | Runtime check | Best for |
|---|---|---|---|---|
| crypto.randomUUID() | v4 | Template literal | Built-in | General-purpose random IDs, zero deps |
| Branded UUID type | any | Nominal typing | No (compile-time) | Prevent UserId / OrderId mix-ups |
| z.string().uuid() / z.uuid() | any | Inferred string | Yes (Zod) | API request / response validation |
| uuidv7() from uuid | v7 | string (typed import) | Package | Time-ordered database primary keys |
| uuidv5() from uuid | v5 | string (typed import) | Package | Deterministic namespace-based IDs |
Environment Compatibility
crypto.randomUUID() availability and TypeScript typing across runtimes.
| Runtime | v4 support | TypeScript notes |
|---|---|---|
| Browser (Vite, Next.js) | ES2021+ with DOM lib | Secure context (HTTPS) required in production |
| Node.js 19+ | Global crypto.randomUUID() | Requires @types/node |
| Node.js 14.17–18 | import { randomUUID } from 'node:crypto' | See Node.js guide |
| Deno | 1.x+ global | Built-in TypeScript, no @types needed |
| Bun | 1.x+ global | Native TS execution, Web Crypto compatible |
| Web Workers | Yes | Same DOM lib typing as main thread |
All UUID Versions
UUID v4 - Random (native, no deps)
// Return type: `${string}-${string}-${string}-${string}-${string}`
const id = crypto.randomUUID();
// → "550e8400-e29b-41d4-a716-446655440000"UUID v7 - Time-ordered (uuid package)
import { v7 as uuidv7 } from 'uuid';
const id: string = uuidv7();
// → "018e8f6a-1b2c-7d3e-9f4a-5b6c7d8e9f0a"
// Later IDs sort after earlier ones (RFC 9562)UUID v5 - Deterministic namespace
import { v5 as uuidv5 } from 'uuid';
const id = uuidv5('example.com', uuidv5.DNS);
// → always "cfbff0d1-9375-5685-968c-48ce8b15ae17"UUID v1 / v3 / v6 - uuid package
import { v1 as uuidv1, v3 as uuidv3, v6 as uuidv6 } from 'uuid';
uuidv1(); // timestamp + MAC-based node
uuidv3('hello', uuidv3.URL); // MD5 namespace hash
uuidv6(); // reordered v1 for better DB locality (RFC 9562)Validate and Parse UUIDs
TypeScript types do not validate runtime input. Always check UUIDs from HTTP requests, query params, and external APIs.
Type guard with is predicate
type UUID = string & { readonly _brand: 'UUID' };
const UUID_RE =
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
function isUUID(value: unknown): value is UUID {
return typeof value === 'string' && UUID_RE.test(value);
}
function toUUID(value: unknown): UUID {
if (!isUUID(value)) throw new TypeError('Invalid UUID');
return value;
}Assertion predicate with asserts
function assertUUID(value: unknown): asserts value is UUID {
if (!isUUID(value)) {
throw new TypeError(`Expected UUID, got ${typeof value}`);
}
}
function handleRequest(body: { id: unknown }) {
assertUUID(body.id);
// body.id is narrowed to UUID here
}Zod validation - Zod 3 and Zod 4
import { z } from 'zod';
// Zod 3
const RequestSchemaV3 = z.object({
userId: z.string().uuid(),
orderId: z.string().uuid(),
});
// Zod 4
const RequestSchemaV4 = z.object({
userId: z.uuid(),
orderId: z.uuid(),
});
type RequestBody = z.infer<typeof RequestSchemaV3>;
function handleRequest(body: unknown): RequestBody {
return RequestSchemaV3.parse(body);
}Branded Types Deep Dive
Branded types add a phantom _brand property that exists only in the type system. At runtime the value is a plain string - zero overhead, full compile-time safety.
// Generic brand helper
type Brand<T, B extends string> = T & { readonly _brand: B };
type UUID = Brand<`${string}-${string}-${string}-${string}-${string}`, 'UUID'>;
type UserId = Brand<UUID, 'UserId'>;
type OrderId = Brand<UUID, 'OrderId'>;
type ProductId = Brand<UUID, 'ProductId'>;
// Factory functions - single cast point
function newUserId(): UserId { return crypto.randomUUID() as UserId; }
function newOrderId(): OrderId { return crypto.randomUUID() as OrderId; }
function newProductId(): ProductId { return crypto.randomUUID() as ProductId; }
// satisfies - verify shape without widening
const config = {
defaultUserId: newUserId(),
maxOrders: 100,
} satisfies { defaultUserId: UserId; maxOrders: number };Real-World Use Cases
Typed entity IDs - prevent ID mix-ups at compile time
type UserId = string & { readonly _brand: 'UserId' };
type OrderId = string & { readonly _brand: 'OrderId' };
interface User { id: UserId; name: string; }
interface Order { id: OrderId; userId: UserId; total: number; }
function getOrder(id: OrderId): Promise<Order> { /* ... */ }
const userId = crypto.randomUUID() as UserId;
const orderId = crypto.randomUUID() as OrderId;
getOrder(orderId); // ✓ OK
// getOrder(userId); // ✗ TypeScript errorAPI response typing with Zod inference
import { z } from 'zod';
const ApiResponseSchema = z.object({
id: z.string().uuid(),
createdAt: z.string().datetime(),
name: z.string(),
});
type ApiResponse = z.infer<typeof ApiResponseSchema>;
async function fetchItem(id: string): Promise<ApiResponse> {
const res = await fetch(`/api/items/${id}`);
const data = await res.json();
return ApiResponseSchema.parse(data); // throws if UUID is invalid
}Discriminated union with UUID brands
type ProductId = string & { readonly _brand: 'ProductId' };
type CategoryId = string & { readonly _brand: 'CategoryId' };
type EntityRef =
| { type: 'product'; id: ProductId }
| { type: 'category'; id: CategoryId };
function resolveEntity(ref: EntityRef) {
switch (ref.type) {
case 'product': return fetchProduct(ref.id);
case 'category': return fetchCategory(ref.id);
}
}Express / Fastify route param validation
import { z } from 'zod';
import type { Request, Response } from 'express';
const ParamsSchema = z.object({ id: z.string().uuid() });
app.get('/users/:id', (req: Request, res: Response) => {
const { id } = ParamsSchema.parse(req.params);
// id is a validated UUID string
return res.json({ id });
});UUID v4 vs v7 Decision Guide
Use v4 by default with crypto.randomUUID(). Switch to v7 when insert order and index locality matter at database scale.
- →UUID v7 Generator - interactive v7 generation and export.
- →Format comparison - v4 vs v7 vs ULID vs NanoID.
- →JavaScript guide - runtime fundamentals and browser compatibility.
How UUID v4 Works in TypeScript
lib.dom.d.ts types crypto.randomUUID() as returning a template literal type. The OS CSPRNG fills 122 random bits; six bits encode version 4 and the RFC 4122 variant.
f47ac10b-58cc-4372-a567-0e02b2c3d479
- f47ac10b
- 32 random bits
- 58cc
- 16 random bits
- 4372
- version 4 + 12 random bits
- a567
- variant 10 + 14 random bits
- 0e02b2c3d479
- 48 random bits
TypeScript inferred type: `${string}-${string}-${string}-${string}-${string}`
With 2^122 possible values, collision risk is negligible for any practical application. Branded types and Zod schemas add safety layers on top of this format without changing the underlying 128-bit structure.
Common Mistakes
Using string instead of branded UUID types
Typing all IDs as string means TypeScript cannot catch passing a userId where an orderId is expected. Branded types cost nothing at runtime.
Not validating incoming UUIDs at runtime
TypeScript types are erased at compile time. Validate HTTP input with z.string().uuid(), z.uuid(), or an isUUID() guard before trusting it.
Unsafe as UserId casts without validation
Casting any string to a branded type bypasses both compile-time and runtime checks. Validate first, then cast: UUIDSchema.parse(input) as UserId.
Missing DOM or ES2021 in tsconfig lib
Without "DOM" and "ES2021" in compilerOptions.lib, crypto.randomUUID() may be untyped or cause TS errors in browser projects.
Installing @types/uuid on uuid v9+
The uuid package ships its own types since v9. Installing @types/uuid on modern versions can cause conflicting type definitions.
Trusting compile-time types for external data
Annotating a function parameter as UserId does not validate the caller's input. Always parse and validate at system boundaries.
Best Practices, Performance, and Security
Best practices
Create branded types for each entity ID - prevents accidental ID mix-ups at compile time.
Validate incoming UUIDs with Zod at API boundaries - types are erased at runtime.
Use crypto.randomUUID() for v4 - no extra package in modern TS projects.
Centralize UUID creation in factory functions - single cast point per branded type.
Performance
Native crypto.randomUUID() typically generates millions of IDs per second (exact throughput varies by OS CSPRNG and hardware). TypeScript types are compile-time only - branded types add zero runtime overhead.
Bulk generation: Array.from({ length: n }, () => crypto.randomUUID()) is idiomatic and efficient.
Security
Entropy source: OS CSPRNG via Web Crypto - same as JavaScript. Cryptographically secure for session tokens, CSRF tokens, and idempotency keys.
Runtime validation: Always validate UUIDs from external sources. TypeScript branded types provide compile-time safety only - they do not protect against malformed HTTP input.
Secure context: Browser TypeScript apps require HTTPS (or localhost). Never use Math.random() for security-sensitive identifiers.
Installation
UUID v4 (native)
# No installation needed for v4Available in Node 19+, browsers since 2021, Deno, and Bun. Set tsconfig lib accordingly.
uuid + zod packages
npm install uuid zod
# or: pnpm add uuid zod / yarn add uuid zodimport { v7 as uuidv7 } from 'uuid';
import { z } from 'zod';
// uuid ships its own types since v9 - no @types/uuid neededFrequently Asked Questions
How do I generate a UUID in TypeScript?
Call crypto.randomUUID() - it returns a version 4 UUID string typed as a template literal. Wrap it in a branded UUID type for entity-specific safety, and validate external input with Zod or a type guard.
What is the return type of crypto.randomUUID() in TypeScript?
lib.dom.d.ts types it as `${string}-${string}-${string}-${string}-${string}` - a template literal matching the UUID hyphenated format. It is structurally a string at runtime.
How do branded UUID types work in TypeScript?
Branded types add a phantom _brand property that exists only in the type system. UserId and OrderId can both be strings at runtime but are not interchangeable at compile time, preventing accidental ID mix-ups.
Do TypeScript types validate UUIDs at runtime?
No. TypeScript types are erased during compilation. A UUID from an HTTP request is just a string at runtime - validate it with z.string().uuid() (Zod 3), z.uuid() (Zod 4), or an isUUID() type guard before trusting it.
How do I validate a UUID with Zod in TypeScript?
Zod 3: z.string().uuid(). Zod 4: z.uuid(). Both throw ZodError on invalid input. Use z.infer<typeof Schema> to derive typed API response shapes.
Does crypto.randomUUID() work in Node.js TypeScript projects?
Yes. Node.js 19+ exposes crypto.randomUUID() globally. On Node 14.17–18, import { randomUUID } from "node:crypto". Ensure @types/node is installed for typing.
What tsconfig lib is needed for crypto.randomUUID()?
Browser projects: include "DOM" and "ES2021" (or later) in compilerOptions.lib. Node projects: install @types/node - randomUUID is typed in node:crypto and on the global crypto object in Node 19+.
UUID v4 vs v7 in TypeScript - when should I use each?
Use crypto.randomUUID() (v4) for general-purpose random IDs. Use uuidv7() from the uuid package for database primary keys where chronological insert order improves B-tree locality (RFC 9562).
Should I use string or branded types for entity IDs?
Use branded types (string & { readonly _brand: "UserId" }) for domain entity IDs. They cost zero runtime overhead and catch passing a userId where an orderId is expected at compile time.
What RFC standards apply to TypeScript UUID generation?
crypto.randomUUID() produces RFC 4122 version 4 UUIDs. UUID v7 follows RFC 9562 (May 2024). The uuid npm package implements both RFC 4122 and RFC 9562 versions with full TypeScript types since v9.
Key definitions
- UUID
- 128-bit universally unique identifier, usually shown as 36 hex characters with hyphens.
- Branded type
- A nominal type created by intersecting a base type with a phantom brand property. Prevents structurally identical types from being mixed.
- Type erasure
- TypeScript removes all type annotations during compilation. Branded types exist only at compile time.
- Template literal type
- A string type defined by a pattern, e.g. `${string}-${string}-...`. Used by lib.dom.d.ts for crypto.randomUUID() return type.
- CSPRNG
- Cryptographically secure pseudo-random number generator. The entropy source behind crypto.randomUUID().
- RFC 4122
- IETF standard defining UUID versions 1 through 5. Version 4 is random.