Skip to main content

Branded Types

TypeScript
Useful
tip

This principal works perfectly together with a parser / validator. A perfect example would be zod which uses Branded Types to make objects as validated.

Lets assume we want to create a Password type:

type Password = string;

And we have a function which creates a user:

function createUser(username: string, password: Password): void {
// creation of a user
// ...
}

TypeScript would allow us to call the function as follows:

createUser('username', 'some-password');

This is because Password is just a type alias for string. However, we rather would like to pass Passwords to the createUser function which have been validated and meet our password requirements. To achieve this, we can use Branded Types.

To brand our Password type, we simply intersect it with an object:

// '__brand' can be named to your liking.
// Some also name it '__type'.
type Password = string & {__brand: 'Password'};

Now we have a type which we normally wouldn't be able to create since we cant have a string which at the same time is an object.

note

Note that this type doesn't resolve to never which is important. We, without a doubt, can't create never types. However we can tell TypeScript that we know more about a specific variable type than itself.

Let's create another function which takes a password and returns whether its valid or not:

function validatePassword(password: string): password is Password {
if (password.length < 8) return false;
// ... some other checks

return true;
}

We now can use this function to tell TypeScript: "Hey TypeScript. If this function returns true treat the provided parameter password as a Password rather than a string".

info

These type of functions are also called Type Guards in TypeScript.

Now that we have your validate function in place we can call createUser:

const password = '12345678';
if (validatePassword(password)) {
createUser('username', password);
}

If you prefer not to nest your code or rather use guards we can create a function called assertValidPassword:

function assertValidPassword(password: string): asserts password is Password {
if (password.length < 8) {
throw new Error('Password needs to be at least 8 characters long.');
}
}

This function must throw an Error and doesn't return a boolean like the validatePassword function. The assertValidPassword function can now be used once and afterwards TypeScript treats the provided parameter as a Password:

const password = '1234';
assertValidPassword(password);
createUser('username', password);