Branded Types
Branded types (also called nominal types or opaque types) add a phantom property to create distinct types from structurally identical base types.
Definition
type Brand<T, B> = T & { __brand: B };
type USD = Brand<number, "USD">;
type EUR = Brand<number, "EUR">;Examples
type Brand<T, B extends string> = T & { readonly __brand: B };
type USD = Brand<number, "USD">;
type EUR = Brand<number, "EUR">;
function usd(amount: number): USD {
return amount as USD;
}
function eur(amount: number): EUR {
return amount as EUR;
}
function addUSD(a: USD, b: USD): USD {
return (a + b) as USD;
}
const price = usd(100);
const tax = usd(8.5);
const total = addUSD(price, tax); // OK
// addUSD(price, eur(50)); // Error: EUR not assignable to USDtype UserId = Brand<string, "UserId">;
type PostId = Brand<string, "PostId">;
function userId(id: string): UserId { return id as UserId; }
function postId(id: string): PostId { return id as PostId; }
function getUser(id: UserId) { /* ... */ }
function getPost(id: PostId) { /* ... */ }
const uid = userId("user-123");
const pid = postId("post-456");
getUser(uid); // OK
// getUser(pid); // Error: PostId not assignable to UserIdtype Email = Brand<string, "Email">;
type URL = Brand<string, "URL">;
function validateEmail(input: string): Email | null {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(input) ? (input as Email) : null;
}
function validateURL(input: string): URL | null {
try {
new globalThis.URL(input);
return input as URL;
} catch {
return null;
}
}
function sendEmail(to: Email, link: URL) { /* ... */ }
const email = validateEmail("[email protected]");
const url = validateURL("https://example.com");
if (email && url) sendEmail(email, url);Common Use Cases
- 1Preventing accidental mixing of same-shaped IDs
- 2Type-safe currency and unit handling
- 3Validated input types (email, URL, phone)
- 4Domain-driven design with distinct value objects
- 5Preventing unit confusion in scientific computing
Understanding Branded Types
Branded types (also known as nominal types or opaque types) solve a fundamental limitation of TypeScript's structural type system. In TypeScript, two types are compatible if they have the same structure, regardless of their names. This means a UserId (string) and a PostId (string) are interchangeable, which can lead to bugs when IDs are accidentally swapped.
Branding adds a phantom property (typically __brand) to create structurally distinct types from the same base type. Brand<number, "USD"> and Brand<number, "EUR"> are both numbers at runtime, but TypeScript treats them as incompatible types, preventing you from accidentally adding dollars to euros.
The pattern requires constructor functions that cast plain values to branded types. These constructors serve as validation boundaries—the only way to create a branded value is through the constructor, which can perform validation. Once a value is branded, the type system ensures it's used correctly throughout the codebase.
Entity IDs are the most common use case. In any application with multiple entity types, accidentally passing a UserId where a PostId is expected is a real bug that structural typing can't catch. Branded IDs catch this at compile time with zero runtime overhead—the brand property only exists in the type system.
Validated strings are another powerful application. By branding a string as Email only after validation, you encode the validation status in the type. Functions that require validated input accept Email (not string), and the compiler ensures validation happened before the value reaches those functions.
Branded types have no runtime cost. The __brand property exists purely in the type system and is erased during compilation. The runtime values are plain strings, numbers, or whatever the base type is.
Related Types
satisfies operatorThe satisfies operator validates that a value matches a type without widening the value's inferred type, preserving literal types and specific structure.
as const assertionThe as const assertion marks a value as deeply readonly with the most specific literal types possible, preventing type widening.
Conditional TypesConditional types select one of two types based on a condition expressed as an extends clause, enabling type-level branching logic.
infer keywordThe infer keyword declares a type variable within a conditional type's extends clause, extracting and capturing part of a type for use in the true branch.
More Advanced Patterns
Explore TypeScript Types
Browse our complete reference of 30 TypeScript utility types with definitions, examples, and explanations.