Generate UUID in TypeScript
TypeScript uses the same native crypto.randomUUID() as JavaScript, but adds type safety with branded UUID types, type guards, and Zod validation for incoming IDs.
Quick Reference
| Approach | Version | Type Safe | Use Case |
|---|---|---|---|
| crypto.randomUUID() | v4 | Template literal type | General purpose — zero deps, built-in |
| Branded UUID type | any | Nominal typing | Prevent mixing UserId / OrderId at compile time |
| z.string().uuid() | any | Runtime validation | Validate UUIDs from API requests / user input |
| v7 from uuid package | v7 | Yes | Time-ordered database PKs |
Primary Implementation
// TypeScript's built-in type for crypto.randomUUID() return value:
// `${string}-${string}-${string}-${string}-${string}`
// Branded / nominal UUID type — prevents mixing different ID types
type Brand<T, B> = T & { readonly _brand: B };
type UUID = Brand<`${string}-${string}-${string}-${string}-${string}`, 'UUID'>;
// Type-safe UUID factory
function generateUUID(): UUID {
return crypto.randomUUID() as UUID;
}
// Specific entity ID types — UserId and OrderId are not interchangeable
type UserId = Brand<UUID, 'UserId'>;
type OrderId = Brand<UUID, 'OrderId'>;
function createUserId(): UserId { return generateUUID() as UserId; }
function createOrderId(): OrderId { return generateUUID() as OrderId; }
// Type guard for runtime validation
function isUUID(s: string): s is UUID {
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(s);
}
const userId: UserId = createUserId();
const orderId: OrderId = createOrderId();
// TypeScript error: Argument of type 'UserId' is not assignable to 'OrderId'
// processOrder(userId); // ← compile-time error
All UUID Versions
UUID v4 — Random (native, no deps)
// Return type is `${string}-${string}-${string}-${string}-${string}`
const id = crypto.randomUUID();
// → "550e8400-e29b-41d4-a716-446655440000"
UUID v7 — Time-ordered (uuid package)
// npm install uuid @types/uuid
import { v7 as uuidv7 } from 'uuid';
const id: string = uuidv7();
// → "018e8f6a-1b2c-7d3e-9f4a-5b6c7d8e9f0a"
Zod validation — runtime UUID checking
// npm install zod
import { z } from 'zod';
const UUIDSchema = z.string().uuid();
// Parse incoming request params
const RequestSchema = z.object({
userId: z.string().uuid(),
orderId: z.string().uuid(),
});
function handleRequest(body: unknown) {
const { userId, orderId } = RequestSchema.parse(body);
// userId and orderId are validated strings here
}
Real-World Use Cases
1. 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 error — UserId is not OrderId
2. API response typing with UUID fields
import { z } from 'zod';
// Define the shape of an API response
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
}
3. Discriminated union with UUID brands
type ProductId = string & { _brand: 'ProductId' };
type CategoryId = string & { _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); // id is ProductId
case 'category': return fetchCategory(ref.id); // id is CategoryId
}
}
Common Mistakes
Using string instead of a branded UUID type
Typing all IDs as string means TypeScript can't catch you passing a userId where an orderId is expected. Branded types cost nothing at runtime and prevent entire classes of bugs.
Not validating incoming UUIDs at runtime
TypeScript types are erased at runtime. A UUID coming from an HTTP request is just a string — validate it with z.string().uuid() or a type guard before trusting it.
Forgetting @types/uuid when using the uuid package
The uuid npm package ships its own types since v9, but older versions need npm install --save-dev @types/uuid for TypeScript support.
How It Works
TypeScript's lib.dom.d.ts types crypto.randomUUID() as returning `${string}-${string}-${string}-${string}-${string}` — a template literal type that structurally matches the UUID format.
Branded types add a phantom property (_brand) that only exists in the type system. At runtime, the value is still a plain string — zero overhead.
Output Formats
crypto.randomUUID()
f47ac10b-58cc-4372-a567-0e02b2c3d479
TypeScript inferred type
`${string}-${string}-${string}-${string}-${string}`
Branded type
string & { readonly _brand: 'UserId' }
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 needed in modern TS projects.
Performance
Same as JavaScript — 5–10 million UUIDs/second natively. TypeScript types are compile-time only and add zero runtime overhead.
Branded types are purely structural — the runtime value is a plain string with no extra properties.
Installation
# No install needed for v4 (built-in)
npm install uuid # v7, v5, v3
npm install zod # runtime validation
The uuid package ships its own TypeScript types since v9. No separate @types/uuid needed.
Security
Entropy source: Same as JavaScript — crypto.randomUUID() uses the OS CSPRNG. Cryptographically secure.
Always validate UUIDs from external sources at runtime — TypeScript types provide no runtime protection against malformed input.