Template literal types let you define string patterns as types, enabling compile-time string validation and generation.
Imagine you’re building a UI component library, and you want to enforce specific naming conventions for CSS classes. Instead of just trusting developers to type btn or btn-primary correctly, you can define a type that only allows valid class names.
type ButtonSize = 'small' | 'medium' | 'large';
type ButtonColor = 'primary' | 'secondary' | 'danger';
type ButtonClassName = `btn-${ButtonSize}-${ButtonColor}`;
// These are valid:
let validClass1: ButtonClassName = 'btn-medium-primary';
let validClass2: ButtonClassName = 'btn-small-danger';
// This will cause a TypeScript error:
// let invalidClass: ButtonClassName = 'btn-extra-large-primary';
// Error: Type '"btn-extra-large-primary"' is not assignable to type 'ButtonClassName'.
This is powerful because it moves string validation from runtime checks to compile-time errors. Your IDE will immediately flag incorrect usage, preventing bugs before your code even runs.
Let’s break down how this works and what you can do with it.
At its core, a template literal type is a string literal type that uses the backtick syntax (``) you’re familiar with for template literals in JavaScript, but within the type system.
type Greeting = `Hello, ${string}!`;
let helloWorld: Greeting = 'Hello, world!';
// let goodbye: Greeting = 'Goodbye, world!'; // Error: Type '"Goodbye, world!"' is not assignable to type 'Greeting'.
Here, ${string} acts as a placeholder. It signifies that any string can be inserted there, and the resulting type will still conform to Greeting. You can also use union types for more specific patterns:
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type ApiEndpoint = `/api/${string}`;
// This is valid:
let usersEndpoint: ApiEndpoint = '/api/users';
// This is also valid:
let productsEndpoint: ApiEndpoint = '/api/products/123';
// This is NOT valid:
// let invalidEndpoint: ApiEndpoint = 'users';
// Error: Type '"users"' is not assignable to type 'ApiEndpoint'.
The magic happens when you combine these placeholders with union types. TypeScript will essentially perform a Cartesian product of the possibilities.
Consider a type for event names that can be a click or hover event on a button or input element:
type InteractiveElement = 'button' | 'input';
type MouseEvent = 'click' | 'hover';
type InteractiveEvent = `${MouseEvent}on${Capitalize<InteractiveElement>}`;
// Valid events:
let buttonClick: InteractiveEvent = 'clickonButton';
let inputHover: InteractiveEvent = 'hoveronInput';
// Invalid events:
// let invalidEvent: InteractiveEvent = 'clickonDiv'; // Error
// let anotherInvalid: InteractiveEvent = 'mousedownonButton'; // Error
Notice the Capitalize<InteractiveElement> part. This is a built-in utility type that takes the first character of a string literal type and capitalizes it. This allows for more sophisticated string manipulation directly within your types. TypeScript has several of these: Uppercase, Lowercase, and Uncapitalize.
The real power comes when you start inferring these types. You can use template literal types in conjunction with mapped types and conditional types to transform existing types or extract specific parts of string literals.
Let’s say you have a configuration object where keys represent different routes and values represent the HTTP methods allowed for those routes. You want to generate a type that lists all possible (method, route) pairs.
type RouteConfig = {
'/users': 'GET' | 'POST';
'/products': 'GET' | 'PUT';
'/orders': 'GET' | 'DELETE';
};
// This is where it gets interesting:
type RouteMethodPair = {
[K in keyof RouteConfig]: `${K & string} ${RouteConfig[K]}`;
}[keyof RouteConfig];
// K in keyof RouteConfig iterates through '/users', '/products', '/orders'
// K & string ensures K is treated as a string for concatenation
// RouteConfig[K] gives us the union of methods for that route
// The result is a union of strings like:
// '/users GET' | '/users POST' | '/products GET' | '/products PUT' | '/orders GET' | '/orders DELETE'
let userGet: RouteMethodPair = '/users GET';
let orderDelete: RouteMethodPair = '/orders DELETE';
// let invalidPair: RouteMethodPair = '/users PUT'; // Error
The [keyof RouteConfig] at the end is a common pattern to flatten the resulting mapped type into a union of its values. Without it, you’d get an object where keys are the original routes and values are the generated strings.
You can also use template literal types to parse strings. Imagine you have a string representing a date in YYYY-MM-DD format and you want to extract the year, month, and day as separate string literals.
type DateString = '2023-10-27';
type ExtractDateParts<T extends string> =
T extends `${infer Year}-${infer Month}-${infer Day}`
? { year: Year; month: Month; day: Day }
: never;
type ParsedDate = ExtractDateParts<DateString>;
// ParsedDate is { year: '2023'; month: '10'; day: '27' }
// Let's try a different format:
type AnotherDateString = '2024-01-15';
type ParsedAnotherDate = ExtractDateParts<AnotherDateString>;
// ParsedAnotherDate is { year: '2024'; month: '01'; day: '15' }
// This won't match the pattern:
// type BadDateString = '27/10/2023';
// type ParsedBadDate = ExtractDateParts<BadDateString>; // Type 'never'
The infer keyword is crucial here. It allows you to "capture" parts of the string that match the template literal pattern. This is incredibly useful for creating type-level parsers and validators for structured string data.
The most subtle, yet often overlooked, aspect of template literal types is how they interact with the string type. When you use ${string} in a template literal type, it matches any string. However, when you combine it with specific string literals or unions, TypeScript becomes much more precise. For instance, Hello, ${string} is a broad type, but Hello, ${'world' | 'there'} is a much narrower union of 'Hello, world' and 'Hello, there'. This precision is what enables the strong compile-time guarantees.
The next frontier you’ll explore is combining template literal types with generics for reusable string manipulation utilities.