TypeScript branded string type utility for type safety

Here’s an easy way to get branded string types in TypeScript. This allows the compiler to enforce minimal type safety on strings with an identity such as a User ID or Post ID.

export type Brand<K, T extends string> = K & { [P in T]: never };

This can be helpful when you have multiple kinds of IDs that are all strings at runtime, and you want to avoid accidentally passing them in the wrong place.

It also lets types in consuming code offer some documentation of interfaces, for example:

function getPostById(id: PostId): Post {
    // ...
}

When interfaces involve multiple different kinds of ID, this can make it easier to follow.

You need to explicitly cast to the branded type at the boundary where you create the value, but that in itself can be helpful in catching mistakes and making intentions explicit.

For input, the branded type casting can be automated with a validation library such as Zod, for example:

import * as z from "zod";

import { PostId, TagId } from "../util/brand.type";

const TagPostValidator = z.object({
    postId: z.uuid().transform((s) => s as PostId),
    tagId: z.uuid().transform((s) => s as TagId),
});

// ...

const { postId, tagId } = TagPostValidator.parse({
    postId: "0c1b665e-059b-4e27-bf45-c960b535bcdb",
    tagId: "308ffcb7-f027-4b4e-b956-bfdce4b70567"
});

Now the postId and tagId variables are typed as PostId and TagId respectively, as well as being validated as UUIDs.

A quick breakdown of K & { [P in T]: never }:

So at runtime the value is still just a string, but at compile time the type carries an extra “tag” that makes Brand<string, "UserId"> incompatible with Brand<string, "PostId">.


Tech mentioned