Description
🔍 Search Terms
"brand", "brand type", "branded", "branded types", "structural brand", "tag type"
✅ Viability Checklist
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
⭐ Suggestion
Add a brand
keyword to TypeScript for defining structurally branded types based on primitives (e.g., string
, number
, bigint
). This feature introduces compile-time type distinction without runtime overhead, ensuring that branded types remain structurally compatible with their base types for operations while being distinct in assignment contexts. For example, UserId
and ProductId
, both branded from number
, cannot be assigned to each other but can be used in arithmetic operations together.
📃 Motivating Example
TypeScript’s structural typing is powerful but can cause issues when types that are structurally identical represent different semantic concepts, like UserId
and ProductId
both being number
. Developers currently use intersection types (e.g., number & { __brand: 'UserId' }
) to enforce distinction, but this approach is cumbersome and leads to confusing error messages. The brand
keyword provides a clean, native solution that enhances type safety and developer experience. Here’s how it improves the language:
brand type UserId = number;
brand type ProductId = number;
let userId: UserId = 1 as UserId;
let productId: ProductId = 2 as ProductId;
userId = productId; // Error: Type 'ProductId' is not assignable to type 'UserId'
let total: number = userId + productId; // OK: Both are structurally numbers
This feature eliminates the need for hacks, provides clear error messages, and aligns with TypeScript’s structural typing philosophy.
💻 Use Cases
-
What do you want to use this for?
To safely distinguish between different kinds of identifiers, measurements, or other primitive-based types that should not be interchangeable, such asUserId
vs.ProductId
, orKilometers
vs.Miles
. -
What shortcomings exist with current approaches?
- The intersection type pattern is verbose and results in error messages that reference internal branding properties, which can be confusing.
- Using wrapper classes introduces unnecessary runtime complexity and overhead, especially for primitives.
-
What workarounds are you using in the meantime?
- Employing intersection types like
type UserId = number & { __brand: 'UserId' };
to achieve type distinction. - Creating constructor functions to enforce branding, but still relying on intersection types for the actual distinction.
- Employing intersection types like
The brand
keyword addresses these issues by offering a straightforward, language-supported way to define and enforce branded types, improving both safety and usability in TypeScript projects.