Template Literal Types
Template literal types use the same backtick syntax as JavaScript template strings, but they operate on types: you interpolate other types into a string pattern to produce new string literal types. When you interpolate a union, the result is the cross product of every combination, which makes them ideal for modeling structured strings like event names, CSS properties, and route paths. They pair naturally with the intrinsic string-manipulation types and with mapped types to transform keys. This page covers the syntax, union expansion, the built-in casing helpers, and parsing patterns with infer.
Basic syntax
Wrap a pattern in backticks and embed types with ${...}. Embedding a single literal produces a single new literal type.
type World = "world";
type Greeting = `hello ${World}`; // "hello world"
type Lang = "en" | "fr";
type Welcome = `welcome-${Lang}`; // "welcome-en" | "welcome-fr"
Greeting is the literal type "hello world". Only string, number, bigint, boolean, null, and undefined (plus literal types of those) can be interpolated, because each must have a string representation.
Union expansion (cross products)
When more than one interpolated position is a union, TypeScript generates every combination. This grows multiplicatively, so it is powerful but worth using deliberately.
type Vertical = "top" | "bottom";
type Horizontal = "left" | "right";
type Corner = `${Vertical}-${Horizontal}`;
// "top-left" | "top-right" | "bottom-left" | "bottom-right"
Two unions of two members each produce four literals. This is the canonical way to enumerate a constrained set of structured strings without writing each one out, and it stays in sync if a source union changes.
Built-in intrinsic casing types
TypeScript ships four intrinsic types that transform the casing of string literals: Uppercase, Lowercase, Capitalize, and Uncapitalize. They are commonly combined with template literals.
type Loud = Uppercase<"hello">; // "HELLO"
type Quiet = Lowercase<"HELLO">; // "hello"
type Title = Capitalize<"hello">; // "Hello"
type Lower = Uncapitalize<"Hello">; // "hello"
type Event = "click" | "focus";
type Handler = `on${Capitalize<Event>}`; // "onClick" | "onFocus"
Handler derives React-style event handler names directly from an event union. These intrinsics are implemented in the compiler itself, so they work purely at the type level with no runtime cost.
| Intrinsic | Input | Output |
|---|---|---|
Uppercase<S> | "abc" | "ABC" |
Lowercase<S> | "ABC" | "abc" |
Capitalize<S> | "abc" | "Abc" |
Uncapitalize<S> | "Abc" | "abc" |
Remapping keys in mapped types
Template literals are most useful when remapping keys via the as clause of a mapped type. This is how you can generate getter names or prefixed keys from an existing type.
type Getters<T> = {
[K in keyof T & string as `get${Capitalize<K>}`]: () => T[K];
};
interface Person {
name: string;
age: number;
}
type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number }
The clause as \get${Capitalizerenames each keyKto a getter name, while() => T[K]types the value as a function returning the original property type. Intersectingkeyof Twithstringexcludes anynumber/symbol` keys that could not be interpolated.
Parsing strings with infer
Combine template literal types with infer inside a conditional to pull structured data back out of a string. This enables type-level parsing of routes, query strings, and identifiers.
type ExtractParam<T> = T extends `/${string}/:${infer Param}` ? Param : never;
type P = ExtractParam<"/users/:id">; // "id"
// Split on a delimiter recursively
type Split<S extends string, D extends string> =
S extends `${infer Head}${D}${infer Tail}`
? [Head, ...Split<Tail, D>]
: [S];
type Parts = Split<"a.b.c", ".">; // ["a", "b", "c"]
ExtractParam matches a route shape and captures the segment after : as Param. Split recursively peels off everything before the delimiter and recurses on the rest, building a tuple — a small but complete type-level parser.
Large cross products can explode combinatorially. TypeScript caps a union at 100,000 members and will error past that, so avoid multiplying many wide unions together.
Best Practices
- Model structured strings (events, routes, CSS keys) with template literal types instead of bare
string. - Use the intrinsic
Capitalize/Uppercasefamily for casing instead of hand-listing variants. - Combine template literals with
askey remapping to generate derived key names. - Intersect
keyof T & stringbefore interpolating keys to exclude non-string keys. - Pair template patterns with
inferto parse and validate string formats at the type level. - Watch out for combinatorial blow-up when interpolating multiple wide unions.
- Keep recursive string parsers tail-shaped to avoid hitting recursion depth limits.