TypeScript

Template Literal Types in TypeScript

I once spent an afternoon debugging a function that expected a URL string but received a bare domain name. No protocol, no slashes. The code ran fine unti...

23 Apr 2024

Template Literal Types in TypeScript

I once spent an afternoon debugging a function that expected a URL string but received a bare domain name. No protocol, no slashes. The code ran fine until it didn't.

Template literal types let you encode string structure into the type system. Instead of accepting any string, you describe the shape a string must have. The compiler rejects anything that doesn't match.

What they do

A template literal type works like a template literal in JavaScript, but at the type level. You define a pattern, and TypeScript enforces it at compile time.

Typescript
type URL = `${string}://${string}`;

const valid: URL = "https://gazar.dev";   // works
const broken: URL = "gazar.dev";          // compile error

The type URL says: "some string, then ://, then some string." Anything else is rejected before the code ever runs.

More patterns

You can mix string and number placeholders to model real formats:

Typescript
type CreditCardSuffix = `XXXX-XXXX-XXXX-${number}`;
type EmailAddress = `${string}@${string}.${string}`;
type DateString = `${number}-${number}-${number}`;
type IpAddress = `${number}.${number}.${number}.${number}`;

You can also combine them with union types:

Typescript
type Color = "red" | "green" | "blue";
type CSSColor = `--color-${Color}`;
// results in "--color-red" | "--color-green" | "--color-blue"

The trade-off

Template literal types catch structural mistakes early. That's the benefit.

The cost: they can only validate shape, not semantics. "999.999.999.999" passes the IpAddress type above just fine. And complex patterns can make type errors hard to read.

Use them when the string format matters and the pattern is simple. For deep validation, runtime checks are still your friend.