Blog
Wild & Free Tools

UUID in TypeScript — Type-Safe UUIDs with Branded Types

Last updated: January 2026 6 min read
Quick Answer

Table of Contents

  1. The Problem: All UUIDs Are Just string
  2. Branded UUID Types
  3. Runtime Validation with Type Guards
  4. UUID Types in Database and API Layers
  5. Zod Integration for UUID Validation
  6. Frequently Asked Questions

In TypeScript, both orderId: string and userId: string are just string — the compiler cannot tell you if you accidentally pass an order ID where a user ID is expected. Branded types turn this into a compile-time error. This guide shows the complete pattern: define branded UUID types, generate them, validate them, and prevent cross-type bugs entirely.

The Problem: All UUIDs Are Just string

// Without branded types — TypeScript allows this bug:
function getOrder(orderId: string) { /* ... */ }
function getUser(userId: string) { /* ... */ }

const orderId = crypto.randomUUID();
const userId = crypto.randomUUID();

getUser(orderId); // TypeScript says: OK ✓ — but this is a bug!

// Both are strings — the compiler cannot distinguish them.
// This bug reaches production.

Because TypeScript uses structural typing — two types are compatible if they have the same shape — string equals string, regardless of what the string represents. Branded types add a discriminator that breaks this structural equivalence.

Branded UUID Types — The Pattern

// Define a generic Brand type:
type Brand<T, B> = T & { readonly __brand: B };

// Define specific UUID types:
type OrderId = Brand<string, 'OrderId'>;
type UserId  = Brand<string, 'UserId'>;
type ProductId = Brand<string, 'ProductId'>;

// Factory functions — the only way to create branded UUIDs:
function createOrderId(): OrderId {
  return crypto.randomUUID() as OrderId;
}
function createUserId(): UserId {
  return crypto.randomUUID() as UserId;
}

// Now TypeScript enforces the distinction:
function getOrder(orderId: OrderId) { /* ... */ }
function getUser(userId: UserId) { /* ... */ }

const orderId = createOrderId();
const userId  = createUserId();

getOrder(orderId); // ✓ OK
getOrder(userId);  // ✗ Error: Argument of type 'UserId' is not assignable to parameter of type 'OrderId'
getUser(orderId);  // ✗ Error: Argument of type 'OrderId' is not assignable to parameter of type 'UserId'

The __brand property only exists in TypeScript's type system — it is erased at runtime. There is no actual __brand field on the string value. This means zero runtime overhead.

Sell Custom Apparel — We Handle Printing & Free Shipping

Runtime UUID Validation and Type Guards

const UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;

// Generic UUID validator:
function isUUID(val: unknown): val is string {
  return typeof val === 'string' && UUID_V4_REGEX.test(val);
}

// Branded type validators:
function isOrderId(val: unknown): val is OrderId {
  return isUUID(val);
  // If you need more than format validation (e.g., check existence in DB),
  // add that here. The type system handles the compile-time guarantees.
}

// Parse from external input (API request, database row):
function parseOrderId(val: unknown): OrderId {
  if (!isOrderId(val)) {
    throw new Error(`Invalid OrderId: ${val}`);
  }
  return val;
}

// Usage in Express route:
app.get('/orders/:id', (req, res) => {
  const orderId = parseOrderId(req.params.id); // throws 400 if invalid
  const order = orderService.getById(orderId); // orderId is OrderId type
  res.json(order);
});

UUID Types Across Database and API Layers

// Database row type — raw strings from DB:
interface OrderRow {
  id: string;       // raw string from database
  status: string;
}

// Domain model — branded types:
interface Order {
  id: OrderId;
  status: string;
}

// Mapper — converts raw strings to branded types:
function mapOrderRow(row: OrderRow): Order {
  return {
    id: parseOrderId(row.id),
    status: row.status,
  };
}

// API response type — serialize back to plain string:
interface OrderResponse {
  id: string;  // plain string for JSON serialization
  status: string;
}

function toOrderResponse(order: Order): OrderResponse {
  return {
    id: order.id as string,  // safe cast — OrderId is a string
    status: order.status,
  };
}

The pattern: raw strings enter at the boundary (database, API request), get validated and branded by a parser, flow through domain logic as typed IDs, and are cast back to plain strings at the output boundary (JSON response). The type system enforces this flow.

UUID Validation with Zod

import { z } from 'zod';

// Zod has a built-in UUID validator:
const UUIDSchema = z.string().uuid();

// With branding:
const OrderIdSchema = z.string().uuid().brand<'OrderId'>();
const UserIdSchema  = z.string().uuid().brand<'UserId'>();

type OrderId = z.infer<typeof OrderIdSchema>;
type UserId  = z.infer<typeof UserIdSchema>;

// Parse and validate at the API boundary:
const ParamsSchema = z.object({
  id: OrderIdSchema,
});

app.get('/orders/:id', (req, res) => {
  const { id } = ParamsSchema.parse(req.params); // throws ZodError if invalid
  // id is OrderId — branded type
  const order = orderService.getById(id);
  res.json(order);
});

Zod's .brand() method combines runtime validation with compile-time branding in a single schema. This is the cleanest approach for TypeScript applications already using Zod for schema validation.

Generate UUID v4 Values for TypeScript Tests

The Cheetah UUID Generator produces RFC-compliant UUID v4 strings — paste them as test fixtures, seed data, or OrderId values in your TypeScript tests.

Open Free UUID Generator

Frequently Asked Questions

What are branded types in TypeScript?

A pattern that adds a phantom type property to a primitive type (like string) to make structurally identical types incompatible. OrderId and UserId are both strings, but branded types make them distinct types the compiler can enforce.

Does crypto.randomUUID() work in TypeScript?

Yes, if your tsconfig.json targets ES2021 or later and your lib includes "DOM" or "WebWorker". In Node.js, it is available since Node 15 without any import. In browsers, it is available in Chrome 92+, Firefox 95+, Safari 15.4+.

Is there a TypeScript UUID library I should use?

The uuid npm package has TypeScript types (@types/uuid is included in uuid v9+). For generation, crypto.randomUUID() is built in and needs no library. For branded types, no library is needed — the pattern in this guide uses only TypeScript features.

Do branded UUID types affect runtime performance?

No. TypeScript types are erased at runtime — the brand property only exists in the type system. At runtime, a branded OrderId is just a plain string. There is no runtime overhead.

How do I configure tsconfig to use crypto.randomUUID()?

Set "target": "ES2021" or later and include "lib": ["ES2021", "DOM"] or "lib": ["ES2021", "WebWorker"] in your tsconfig.json. The DOM lib includes the crypto.randomUUID() type definition.

Chris Hartley
Chris Hartley SEO & Marketing Writer

Chris has been in digital marketing for twelve years covering SEO tools and content optimization.

More articles by Chris →
Launch Your Own Clothing Brand — No Inventory, No Risk