Template Literal Types in TypeScript


Template literal types arrived in TypeScript 4.1 and gave string manipulation a permanent home in the type system. They look exactly like JavaScript's template literals — backticks, ${} interpolation — but they construct types, not values. Combined with mapped types and conditional types, they let you encode string patterns, auto-generate related string types, and validate that function arguments follow naming conventions — all at compile time. In this post, you'll learn the syntax, how distribution over unions works, the built-in string manipulation utility types, and several real-world patterns.
TypeScript 4.1+
Template literal types were introduced in TypeScript 4.1. They're fully supported in all modern TypeScript versions. If you're on an older version and see unexpected errors with backtick types, upgrading TypeScript will resolve them.
What Are Template Literal Types
A template literal type uses backtick syntax and ${} interpolation to construct new string types from existing ones:
type Greeting = `Hello, ${string}`;
// Greeting matches any string starting with "Hello, "
const g1: Greeting = "Hello, World"; // OK
const g2: Greeting = "Hello, Alice"; // OK
// const g3: Greeting = "Hi, Alice"; // Error: doesn't match the patternWhen you interpolate a specific string literal or union type, TypeScript resolves the combinations:
type Direction = "North" | "South";
type Speed = "Fast" | "Slow";
type Movement = `${Speed}${Direction}`;
// "FastNorth" | "FastSouth" | "SlowNorth" | "SlowSouth"Basic Template Literal Type Syntax
The simplest form combines two string literals:
type EventName = `on${string}`;
// Matches "onClick", "onChange", "onSubmit", etc.
type CSSUnit = `${number}px` | `${number}em` | `${number}rem`;
// "10px", "1.5em", "2rem" all match — but TypeScript can't fully verify runtime values
// Combining concrete string literals
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type Endpoint = "/users" | "/posts" | "/comments";
type Route = `${HttpMethod} ${Endpoint}`;
// "GET /users" | "GET /posts" | ... (12 combinations total)You can interpolate any type that has a string representation — string, number, boolean, bigint, and string/number/boolean literals:
type Px<T extends number> = `${T}px`;
type TenPx = Px<10>; // "10px"
type HalfPx = Px<0.5>; // "0.5px"Combining with Union Types
When you interpolate a union type, the template literal type distributes over the union, producing a union of all combinations:
type Axis = "x" | "y" | "z";
type Transform = "translate" | "rotate" | "scale";
type CSSTransform = `${Transform}${Capitalize<Axis>}`;
// "translateX" | "translateY" | "translateZ"
// | "rotateX" | "rotateY" | "rotateZ"
// | "scaleX" | "scaleY" | "scaleZ"This distribution happens for each interpolated position independently — three transforms × three axes = nine combinations, all inferred automatically.
Combinatorial Explosion
Distribution is powerful but can produce very large union types. Three positions each with 10 options = 1000 combinations. TypeScript will still type-check them all, but IDE autocompletion and type error messages become unwieldy. Keep unions small when using multiple interpolation positions.
Intrinsic String Manipulation Types
TypeScript ships four built-in utility types for string casing, backed by compiler intrinsics:
type U = Uppercase<"hello">; // "HELLO"
type L = Lowercase<"HELLO">; // "hello"
type C = Capitalize<"hello">; // "Hello"
type UC = Uncapitalize<"Hello">; // "hello"These work on union types too:
type Colors = "red" | "green" | "blue";
type UpperColors = Uppercase<Colors>; // "RED" | "GREEN" | "BLUE"
type CapColors = Capitalize<Colors>; // "Red" | "Green" | "Blue"Combined with template literals and mapped types, they power patterns like auto-generating getter and setter names:
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type Setters<T> = {
[K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
};
interface UserState {
name: string;
age: number;
active: boolean;
}
type UserGetters = Getters<UserState>;
// { getName: () => string; getAge: () => number; getActive: () => boolean }
type UserSetters = Setters<UserState>;
// { setName: (value: string) => void; setAge: (value: number) => void; ... }Pattern Matching at Type Level
Template literal types can be used with infer to extract parts of a string type:
// Extract the event name from an "on{Event}" string
type ExtractEventName<T extends string> = T extends `on${infer EventName}`
? EventName
: never;
type Click = ExtractEventName<"onClick">; // "Click"
type Change = ExtractEventName<"onChange">; // "Change"
type Raw = ExtractEventName<"submit">; // never — doesn't match
// Extract prefix and suffix from a formatted string
type ParseRoute<T extends string> = T extends `${infer Method} ${infer Path}`
? { method: Method; path: Path }
: never;
type Parsed = ParseRoute<"GET /users">;
// { method: "GET"; path: "/users" }String Parsing at the Type Level
Combining template literals with infer lets you parse string patterns at the
type level — extracting meaningful pieces from formatted strings like routes,
event names, or CSS property strings. This was impossible before TypeScript
4.1.
Real-World Patterns
Here's a complete example — a type-safe event emitter where event handler names are derived from event names:
type EventMap = {
click: { x: number; y: number };
keydown: { key: string; code: string };
resize: { width: number; height: number };
};
type EventHandlers = {
[K in keyof EventMap as `on${Capitalize<string & K>}`]: (
event: EventMap[K]
) => void;
};
// EventHandlers is:
// {
// onClick: (event: { x: number; y: number }) => void;
// onKeydown: (event: { key: string; code: string }) => void;
// onResize: (event: { width: number; height: number }) => void;
// }
class TypedEmitter {
private handlers: Partial<EventHandlers> = {};
on<K extends keyof EventHandlers>(event: K, handler: EventHandlers[K]): void {
this.handlers[event] = handler;
}
}
const emitter = new TypedEmitter();
emitter.on("onClick", ({ x, y }) => console.log(x, y)); // fine
// emitter.on("onMissing", () => {}); // Error: not a valid eventAnother pattern: type-safe CSS-in-JS property names:
type CSSProperty = "margin" | "padding" | "border" | "background";
type CSSDirection = "Top" | "Right" | "Bottom" | "Left";
type DirectionalCSS = `${CSSProperty}${CSSDirection}`;
// "marginTop" | "marginRight" | "paddingTop" | "borderTop" | ... (16 combinations)
function setStyle(property: DirectionalCSS, value: string): void {
document.body.style[property as any] = value;
}
setStyle("marginTop", "16px"); // fine
// setStyle("marginMiddle", "0"); // Error: not a valid CSS propertyBest Practices
Use template literals to encode string conventions as types. If your codebase has naming conventions — event handlers must start with on, API routes must start with /, action types follow ENTITY/ACTION — encode them as template literal types.
Keep union sizes manageable. Two positions with 5 options each = 25 combinations, which is fine. Three positions with 10 options = 1000 combinations, which causes slow type checking and unreadable error messages.
Prefer template literals over regex-style string validation. Template literal types are far more composable and give better error messages than string with a comment saying "must follow this format."
Extract with infer for parsing patterns. Whenever you need to pull a segment out of a typed string, combine template literals with infer in a conditional type.
Anti-Pattern
Don't use template literal types for arbitrary string validation that changes at runtime. They're a compile-time tool — they check literal types, not runtime values. For runtime string validation, use a validation library like Zod.
What Is Next
You've now completed the core building blocks of TypeScript's advanced type system: conditional types, infer, mapped types, keyof/typeof, indexed access, and template literal types. The next series of posts dives into utility types in depth — deconstructing Partial, Required, Readonly, Record, Pick, Omit, Exclude, Extract, and how to compose them into custom utilities.
Key Takeaways
- Template literal types use backtick syntax to construct new string types from existing ones
- When you interpolate a union type, the template literal distributes over each member and produces a union of all combinations
- TypeScript ships four intrinsic string manipulation types:
Uppercase,Lowercase,Capitalize,Uncapitalize - Combining template literals with
inferenables string pattern parsing at the type level - Template literal types are compile-time tools — they validate literal types, not runtime string values
Phase 5 Advanced Generics Complete
You've now mastered the complete advanced generics toolkit: conditional types, infer, mapped types, keyof, typeof, indexed access, and template literals. The patterns these tools enable — type-safe events, auto-generated getters, route typing — are what make production TypeScript codebases a joy to maintain.