Template literal types use the same syntax as JS template literals, but with types interpolated instead of values. The result is a string literal type computed from the constituent parts.
types.tstype World = "world"; type Greeting = `hello ${World}`; // type Greeting = "hello world" type Color = "red" | "green" | "blue"; type ColorVar = `--color-${Color}`; // "--color-red" | "--color-green" | "--color-blue"
Interpolated positions accept any type with a string representation: string, number, bigint,
boolean, null, undefined, or unions thereof. Other types
(objects, arrays) are rejected at the type position.
type Px = `${number}px`;
type Flag = `is-${boolean}`;
// "is-true" | "is-false" (boolean expands to its union)
const a: Px = "10px"; // OK
const b: Px = "10em"; // Error
${number}, ${string})
is a pattern type. It matches the infinite set of strings conforming to that pattern and is
used both for validation and for assignability checks.
When a union type is interpolated, TypeScript distributes over each member, producing the cartesian product of all union combinations across all interpolation slots.
type Direction = "top" | "right" | "bottom" | "left";
type Size = "sm" | "md" | "lg";
type Spacing = `m${Direction}-${Size}`;
// "mtop-sm" | "mtop-md" | "mtop-lg" | "mright-sm" | ... (12 members total)
TypeScript ships four built-in generic types for case transformation, implemented natively by the compiler (not expressible in user-land TS).
| Type | Effect | Example |
|---|---|---|
Uppercase<S> | Converts every character to uppercase | Uppercase<"abc"> // "ABC" |
Lowercase<S> | Converts every character to lowercase | Lowercase<"ABC"> // "abc" |
Capitalize<S> | Uppercases the first character only | Capitalize<"abc"> // "Abc" |
Uncapitalize<S> | Lowercases the first character only | Uncapitalize<"Abc"> // "abc" |
These compose naturally with template literal types:
type EventName<T extends string> = `on${Capitalize}` ;
type ClickEvent = EventName<"click">; // "onClick"
type FocusEvent = EventName<"focus">; // "onFocus"
infer
Template literal types can appear in conditional types, where each interpolation slot becomes
an inference site via infer. This enables pattern matching and decomposition of
string literal types.
type ParseRoute<T extends string> =
T extends `${infer Segment}/${infer Rest}`
? [Segment, ...ParseRoute<Rest>]
: [T];
type Parts = ParseRoute<"users/:id/posts">;
// ["users", ":id", "posts"]
The same mechanism is used to extract route parameters, a common pattern in typed routing libraries:
type ExtractParams<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? Param | ExtractParams<Rest>
: T extends `${string}:${infer Param}`
? Param
: never;
type Params = ExtractParams<"/users/:userId/posts/:postId">;
// "userId" | "postId"
"Type instantiation is excessively deep and possibly infinite".
Template literal types as computed property names ("key remapping") let you derive new object shapes from existing ones — the basis for typed event emitters, getters/setters, and action creators.
type Getters<T> = {
[K in keyof T as `get${Capitalize}` ]: () => T[K]
};
interface Person {
name: string;
age: number;
}
type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number }
The string & K intersection is required because K (from
keyof T) may include symbol or number, which are not
valid template literal interpolations on their own without that narrowing.
type EventMap = {
click: MouseEvent;
keydown: KeyboardEvent;
};
type Handlers = {
[K in keyof EventMap as `on${Capitalize}` ]?:
(ev: EventMap[K]) => void
};
// { onClick?: (ev: MouseEvent) => void; onKeydown?: (ev: KeyboardEvent) => void }
type Unit = "px" | "%" | "rem" | "em";
type CSSValue = `${number}${Unit}` | "0" | "auto";
type PathsOf<T, Prefix extends string = ""> = {
[K in keyof T & string]: T[K] extends object
? PathsOf<T[K], `${Prefix}${K}.`>
: `${Prefix}${K}`
}[keyof T & string];
interface Config {
db: { host: string; port: number };
debug: boolean;
}
type ConfigPath = PathsOf<Config>;
// "db.host" | "db.port" | "debug"
type SelectQuery<T extends string> =
T extends `SELECT ${string} FROM ${string}` ? T : never;
function query<T extends string>(sql: SelectQuery<T>): void {}
query("SELECT * FROM users"); // OK
query("DELETE FROM users"); // Error: argument type 'never'
type Meters = `${number}m`;
type Feet = `${number}ft`;
function toMeters(value: Feet): Meters {
const n = parseFloat(value);
return `${n * 0.3048}m` as Meters;
}
${string} placeholders are ambiguous for inference
purposes — TypeScript resolves infer greedily/lazily based on position, which
can produce unexpected splits in recursive patterns.`${number}px` is a
type-level constraint only; a runtime string must still be checked or cast."Type instantiation is excessively deep" and
"Expression produces a union type that is too complex to represent" errors.symbol and unique symbol cannot be interpolated into a
template literal type.tsc slowdowns. Profile with tsc --extendedDiagnostics
if type-checking time regresses after introducing such a type.