Branded types for IDs
Prevent mixing up UserId and OrderId strings by giving each a unique compile-time brand.
- TypeScript
- GreenSnippets
- MIT
- Executed with Node.js 22 (type stripping)
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
loadUser(userId("usr_1")); // ok
loadUser(orderId("ord_1")); // compile error: OrderId is not assignable to UserIdHow 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.
Related snippets
- TypeScript Tested
Check an object against a type with satisfies
Validate a config object against an interface while keeping its precise literal types.
- TypeScript Tested
Non-empty array type
Encode "at least one element" in the type so head, max and similar functions never see an empty array.
- TypeScript Tested
Assert a value is not null or undefined
An assertion function that narrows away null and undefined and throws a clear error otherwise.
- TypeScript Tested
Typed Object.keys and Object.entries
Helpers that keep key types instead of widening to string, for objects you know the shape of.

