Skip to main content

Searching...

Tools
Articles
View All Results

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
typescript
// 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. 1

    Configure your TypeScript environment

    Browser projects need "DOM" and "ES2021" (or later) in compilerOptions.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. 2

    Generate a typed UUID v4

    Call crypto.randomUUID() and cast to your branded UUID type. In Node.js 14.17–18, import from node:crypto instead.

    typescript
    type 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. 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.

    typescript
    type 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. 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.

    typescript
    import { 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 libSecure context (HTTPS) required in production
Node.js 19+Global crypto.randomUUID()Requires @types/node
Node.js 14.17–18import { randomUUID } from 'node:crypto'See Node.js guide
Deno1.x+ globalBuilt-in TypeScript, no @types needed
Bun1.x+ globalNative TS execution, Web Crypto compatible
Web WorkersYesSame DOM lib typing as main thread
Secure context requirements for crypto.randomUUID() HTTPS Works HTTP (non-local) TypeError
In browser TypeScript apps, crypto.randomUUID() requires a secure context. localhost counts as secure during development.

All UUID Versions

UUID v4 - Random (native, no deps)

typescript
// Return type: `${string}-${string}-${string}-${string}-${string}`
const id = crypto.randomUUID();
// → "550e8400-e29b-41d4-a716-446655440000"

UUID v7 - Time-ordered (uuid package)

typescript
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

typescript
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

typescript
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

typescript
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

typescript
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

typescript
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.

typescript
// 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 };
TypeScript type erasure: branded types exist only at compile time Compile time UserId (branded) Runtime "f47ac10b-58cc-..."
Branded types are erased during compilation. The runtime value is a plain string with no extra properties.

Real-World Use Cases

Typed entity IDs - prevent ID mix-ups at compile time

typescript
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

API response typing with Zod inference

typescript
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

typescript
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

typescript
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 version selection flowchart for TypeScript Need an ID? Sortable PK? Deterministic? uuidv7() RFC 9562 v4 default uuidv5() namespace hash
Default to crypto.randomUUID() (v4). Reach for uuidv7() or uuidv5() when sortability or determinism is required.

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.

UUID v4 128-bit layout random 4xxx yxxx random
128-bit layout: version nibble (4) and variant bits (8/9/a/b) are fixed; all other bits are random.
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)

bash
# No installation needed for v4

Available in Node 19+, browsers since 2021, Deno, and Bun. Set tsconfig lib accordingly.

uuid + zod packages

bash
npm install uuid zod
# or: pnpm add uuid zod / yarn add uuid zod
typescript
import { v7 as uuidv7 } from 'uuid';
import { z } from 'zod';
// uuid ships its own types since v9 - no @types/uuid needed

Frequently 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.