TypeScript was designed to make your code safer and more reliable, especially if you compare it to the good ol’ JavaScript. But even TypeScript codebases typically have room for improvement.
Today we’ll talk about one technique that can definitely improve your code: Type Branding. Branded Types are specific and precise types that go beyond JavaScript primitives, offering greater clarity and specificity.
Too abstract? Don’t worry! Let’s see a few examples and you’ll quickly get the gist of it.
The Problems that Branded Types Solve
Example 1
As you already know, TypeScript is basically a language designed to annotate JavaScript code with types. This ensures that the data we pass through our application has the correct type. Unfortunately, more often than not, the types we use aren’t specific enough, which can lead to unexpected runtime errors that, ideally, should have been detected at compilation time.
For example, let’s consider a simple WordPress plugin that retrieves the comments a user made in a certain post. Something like this:
type User = { id: number, ... };
type Post = { id: number, ... };
type Comment = { id: number, ... };
async function getUserCommentsInPost( postId: number, userId: number ): Comment[] {
return apiFetch( {
path: `/nelio/v1/post/${ postId }/comments?author=${ userId }`,
} );
}Code language: TypeScript (typescript)As you can see, all our types have an id field that’s a number (pretty common in WordPress). And, as expected, our getUserCommentsInPost function takes two arguments, a postId and a userId, both numeric. Nothing fancy, no surprises here… So let’s use this function:
declare const user: User;
declare const post: Post;
const comments = getUserCommentsInPost( user.id, post.id );Code language: TypeScript (typescript)Extremely simple example that works as expected… because it does work as expected, doesn’t it?
Well, I’m afraid it doesn’t. Look at the function signature and then our invocation. See the bug? You’re supposed to provide a postId first and a userId next, but we did it the other way around: user.id followed by post.id.
Example 2
Sometimes, the functions we create make certain assumptions about the data they’re given. And, if those assumptions aren’t true, our code would crash at runtime.
For instance, consider the classic example of a divide function:
/**
* Returns the result of a divided by b.
*
* @param number a the numerator.
* @param nubmer b the denominator (must be different than 0).
*
* @return number the result of a divided by b.
*/
function divide( a: number, b: number ): number {
return a / b;
}Code language: TypeScript (typescript)that, given two numbers a and b, it returns the result of a divided by b.
As you already know, divisions are undefined when b is 0. Therefore, it seems reasonable to think that, whoever implemented the divide function, assumed that no one will call their function with b set to 0. Heck, they even said so in the documentation! “b must be different than 0.”
Unfortunately, comments can be easily ignored by the programmer, which means they can simply (inadvertently) invoke divide(x,0) and the compiler wouldn’t complain.

Nelio Forms
A fantastic contact plugin using the block editor. In its simplicity lies the true power of this plugin. I love it, very versatile and it worked perfectly for me.

Wajari Velasquez
Branded Types Are The Solution
Now that you’ve seen a couple of examples and you understand the problem better, let’s revisit the definition of a Branded Type: “a specific and precise type that go beyond JavaScript primitives, offering greater clarity and specificity.” Here’s how this definition applies to our examples.
In the first example, we represented the IDs of a Post, User, and Comment as numbers. We’ve seen that this can cause problems down the line, because we’re unable to tell apart post IDs from user IDs. Let’s fix this using specific and precise types:
type UserId = ???;
type PostId = ???;
type CommentId = ???;
type User = { id: UserId, ... };
type Post = { id: PostId, ... };
type Comment = { id: CommentId, ... };
async function getUserCommentsInPost( postId: PostId, userId: UserId ): Comment[] {
return apiFetch( {
path: `/nelio/v1/post/${ postId }/comments?author=${ userId }`,
} );
}Code language: TypeScript (typescript)See? If users have a UserId, posts have a PostId, and UserIds are somehow different from PostIds, then this:
declare const user: User;
declare const post: Post;
const comments = getUserCommentsInPost( user.id, post.id );Code language: TypeScript (typescript)should trigger a compilation error, because user.id is not a PostId and post.id is not a UserId.
In the second example, we have a similar issue. We want to make sure that the denominator is a number different than 0, but the primitive type number can obviously be 0, hence the issue. So, in this instance, we need a specific and precise type that represents the idea of the value being a number other than 0:
type NonZeroNumber = ???;
function divide( a: number, b: NonZeroNumber ): number {
return a / b;
}Code language: JavaScript (javascript)and then we’ll be able to invoke the divide function, provided we can produce an instance of NonZeroNumber.
How to Create Branded Types?
Creating Branded Types in TypeScript is extremely easy. Personally, I like to use a package called ts-brand for that. All you gotta do is import its helper type Brand and then specify two things: on the one hand, the primitive type behind your Branded Type and, on the other hand, a unique tag to uniquely identify it:
import type { Brand } from 'ts-brand';
type UserId = Brand< number, 'UserId' >;
type PostId = Brand< number, 'PostId' >;
type CommentId = Brand< number, 'CommentId' >;
type NonZeroNumber = Brand< number, 'NonZeroNumber' >;Code language: TypeScript (typescript)If you haven’t already, you can see it in action on the playground.
If you don’t want to use this library, creating your own Branded Types is also very easy. All you gotta do is intersect the base type with an object type that has a non-existent property:
type UserId = number & { __type__: 'UserId' };
type PostId = number & { __type__: 'PostId' };
type CommentId = number & { __type__: 'CommentId' };
type NonZeroNumber = number & { __type__: 'NonZeroNumber' };Code language: JavaScript (javascript)and that’s it! If you do so, you’ll see you get the same results as before.
How to Create Instances of a Branded Type?
Finally, let’s go back to our second example, where we expect the second argument of our divide function to be a NonZeroNumber. As we’ve already seen, we know this won’t work:
divide( 3, 2 );Code language: TypeScript (typescript)because 2 is not a NonZeroNumber. I mean, you and I know it is, but the compiler doesn’t—the compiler thinks it’s a number and, therefore, it doesn’t accept it.
Well, the first (and worst) solution would be to use an explicit cast:
const denominator = 2 as NonZeroNumber;
divide( 3, denominator );Code language: TypeScript (typescript)but, in general, this defeats the purpose of using Branded Types. We want the compiler to detect as many errors as possible and explicit casts are a escape hatch that should be seldom used. So what should we do instead?
Well, as we said before, Branded Types help us capture specific assumptions or properties of the values they contain. For example, a NonZeroNumber is a number that’s not 0 (duh). Therefore, the obvious and best solution is to create a type predicate that checks these assumptions/properties and, if they’re all met, declares an instance of the primitive type as a valid instance of the Branded Type:
function isNonZeroNumber( n: number ): n is NonZeroNumber {
return n !== 0;
}Code language: TypeScript (typescript)And now things work as expected:
const denominator = 2;
if ( isNonZeroNumber( denominator ) ) {
divide( 3, denominator );
}
Code language: TypeScript (typescript)Summary
Branded Types make our code safer and more intentional by defining specific and unique types that better capture and represent their values. In this post, we’ve seen a couple of examples where Branded Types can be helpful, but there’s plenty of them:
Uuidinstead ofstringEmailinstead ofstringNaturalNumberinstead ofnumberUrlinstead ofstring- etc
Whenever you see yourself using primitive types, pause for a second and think if a Branded Type could be more helpful in the long run. My experience so far has been that, at least, when it comes to id properties, they always do 😉
What about you? Did you know about them? Do you use them in your projects? Do you have any tips? Let me know in the comment section below!
Featured Image by Angèle Kamp on Unsplash.




Leave a Reply