Skip to content
GreenSnippets

Branded types for IDs

Prevent mixing up UserId and OrderId strings by giving each a unique compile-time brand.

TypeScript
type Brand<T, B extends string> = T & { readonly __brand: B };

type UserId = Brand<string, "UserId">;
type OrderId = Brand<string, "OrderId">;

function userId(value: string): UserId {
  if (!/^usr_[a-z0-9]+$/.test(value)) throw new Error(`Invalid user id: ${value}`);
  return value as UserId;
}

function orderId(value: string): OrderId {
  if (!/^ord_[a-z0-9]+$/.test(value)) throw new Error(`Invalid order id: ${value}`);
  return value as OrderId;
}

function loadUser(id: UserId): string {
  return `user:${id}`;
}

Example / expected output

Output
loadUser(userId("usr_1"));  // ok
loadUser(orderId("ord_1")); // compile error: OrderId is not assignable to UserId

How it works

Two string IDs are interchangeable to the compiler, which hides bugs such as passing an order ID to a user lookup. A brand adds a phantom property that exists only in the type system, so there is no runtime cost. Create branded values through one validating function.