Template literal types combine literal types with string manipulation for powerful type-level string operations.
Basic Syntax#
1// Simple template literal type
2type Greeting = `Hello, ${string}!`;
3
4const valid: Greeting = 'Hello, World!'; // OK
5const valid2: Greeting = 'Hello, TypeScript!'; // OK
6// const invalid: Greeting = 'Hi, World!'; // Error
7
8// With union types
9type Color = 'red' | 'green' | 'blue';
10type ColorClass = `color-${Color}`;
11// 'color-red' | 'color-green' | 'color-blue'
12
13// Multiple unions
14type Size = 'sm' | 'md' | 'lg';
15type SizedColor = `${Size}-${Color}`;
16// 'sm-red' | 'sm-green' | 'sm-blue' | 'md-red' | ... (9 combinations)String Manipulation Types#
1// Built-in string manipulation types
2type Upper = Uppercase<'hello'>; // 'HELLO'
3type Lower = Lowercase<'HELLO'>; // 'hello'
4type Cap = Capitalize<'hello'>; // 'Hello'
5type Uncap = Uncapitalize<'Hello'>; // 'hello'
6
7// With template literals
8type EventName = 'click' | 'focus' | 'blur';
9type Handler = `on${Capitalize<EventName>}`;
10// 'onClick' | 'onFocus' | 'onBlur'
11
12// Screaming case
13type ScreamingCase<S extends string> = Uppercase<S>;
14type Screaming = ScreamingCase<'hello_world'>; // 'HELLO_WORLD'Event Handlers#
1// Generate event handler types
2type DOMEvents = 'click' | 'focus' | 'blur' | 'change' | 'input';
3
4type EventHandlers = {
5 [K in DOMEvents as `on${Capitalize<K>}`]: (event: Event) => void;
6};
7// {
8// onClick: (event: Event) => void;
9// onFocus: (event: Event) => void;
10// ...
11// }
12
13// With proper event types
14type EventMap = {
15 click: MouseEvent;
16 focus: FocusEvent;
17 blur: FocusEvent;
18 change: Event;
19 input: InputEvent;
20};
21
22type TypedEventHandlers = {
23 [K in keyof EventMap as `on${Capitalize<K & string>}`]: (
24 event: EventMap[K]
25 ) => void;
26};CSS Property Types#
1// CSS spacing utilities
2type SpacingScale = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 12;
3type Side = 't' | 'r' | 'b' | 'l' | 'x' | 'y' | '';
4type SpacingClass = `${'m' | 'p'}${Side}-${SpacingScale}`;
5// 'm-0' | 'm-1' | ... | 'mt-0' | 'px-4' | 'py-8' | ...
6
7// Color classes
8type ColorShade = 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900;
9type ColorName = 'red' | 'blue' | 'green' | 'gray';
10type ColorClass = `${ColorName}-${ColorShade}`;
11// 'red-100' | 'red-200' | ... | 'blue-100' | ...
12
13// Background classes
14type BgClass = `bg-${ColorClass}`;
15type TextClass = `text-${ColorClass}`;Route Parameters#
1// Extract route parameters
2type ExtractParams<T extends string> =
3 T extends `${infer Start}:${infer Param}/${infer Rest}`
4 ? Param | ExtractParams<Rest>
5 : T extends `${infer Start}:${infer Param}`
6 ? Param
7 : never;
8
9type UserRouteParams = ExtractParams<'/users/:userId/posts/:postId'>;
10// 'userId' | 'postId'
11
12// Create params object type
13type RouteParams<T extends string> = {
14 [K in ExtractParams<T>]: string;
15};
16
17type UserParams = RouteParams<'/users/:userId/posts/:postId'>;
18// { userId: string; postId: string }
19
20// Type-safe route builder
21function createRoute<T extends string>(
22 template: T,
23 params: RouteParams<T>
24): string {
25 let result: string = template;
26 for (const [key, value] of Object.entries(params)) {
27 result = result.replace(`:${key}`, value as string);
28 }
29 return result;
30}
31
32createRoute('/users/:userId/posts/:postId', {
33 userId: '123',
34 postId: '456'
35});API Endpoints#
1// REST endpoints
2type Method = 'GET' | 'POST' | 'PUT' | 'DELETE';
3type Resource = 'users' | 'posts' | 'comments';
4
5type Endpoint = `${Method} /api/${Resource}`;
6// 'GET /api/users' | 'POST /api/users' | ...
7
8// With IDs
9type EndpointWithId = `${Method} /api/${Resource}/:id`;
10
11// Type-safe API client
12type ApiRoutes = {
13 'GET /users': { response: User[] };
14 'GET /users/:id': { params: { id: string }; response: User };
15 'POST /users': { body: CreateUser; response: User };
16 'PUT /users/:id': { params: { id: string }; body: UpdateUser; response: User };
17 'DELETE /users/:id': { params: { id: string }; response: void };
18};
19
20type ExtractMethod<T extends string> =
21 T extends `${infer M} ${string}` ? M : never;
22
23type ExtractPath<T extends string> =
24 T extends `${string} ${infer P}` ? P : never;Property Getters/Setters#
1// Generate getter/setter types
2type Getters<T> = {
3 [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
4};
5
6type Setters<T> = {
7 [K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
8};
9
10interface Person {
11 name: string;
12 age: number;
13}
14
15type PersonGetters = Getters<Person>;
16// { getName: () => string; getAge: () => number }
17
18type PersonSetters = Setters<Person>;
19// { setName: (value: string) => void; setAge: (value: number) => void }
20
21type PersonWithAccessors = Person & Getters<Person> & Setters<Person>;Redux Action Types#
1// Action type strings
2type ActionDomain = 'user' | 'post' | 'comment';
3type ActionVerb = 'fetch' | 'create' | 'update' | 'delete';
4type ActionState = 'request' | 'success' | 'failure';
5
6type ActionType = `${ActionDomain}/${ActionVerb}_${ActionState}`;
7// 'user/fetch_request' | 'user/fetch_success' | ...
8
9// Action type constants
10type ActionTypes<D extends string, V extends string> = {
11 [K in `${Uppercase<V>}_REQUEST` | `${Uppercase<V>}_SUCCESS` | `${Uppercase<V>}_FAILURE`]:
12 `${D}/${Lowercase<K>}`;
13};
14
15type UserFetchActions = ActionTypes<'user', 'fetch'>;
16// {
17// FETCH_REQUEST: 'user/fetch_request';
18// FETCH_SUCCESS: 'user/fetch_success';
19// FETCH_FAILURE: 'user/fetch_failure';
20// }Parsing Strings#
1// Parse dot notation
2type ParseDotPath<T extends string> =
3 T extends `${infer Head}.${infer Tail}`
4 ? [Head, ...ParseDotPath<Tail>]
5 : [T];
6
7type Path = ParseDotPath<'user.address.city'>;
8// ['user', 'address', 'city']
9
10// Parse query string
11type ParseQueryString<T extends string> =
12 T extends `${infer Key}=${infer Value}&${infer Rest}`
13 ? { [K in Key]: Value } & ParseQueryString<Rest>
14 : T extends `${infer Key}=${infer Value}`
15 ? { [K in Key]: Value }
16 : {};
17
18type Query = ParseQueryString<'name=john&age=30&active=true'>;
19// { name: 'john'; age: '30'; active: 'true' }Validation Patterns#
1// Email-like pattern (simplified)
2type EmailPattern = `${string}@${string}.${string}`;
3
4function sendEmail(to: EmailPattern) {
5 // ...
6}
7
8sendEmail('user@example.com'); // OK
9// sendEmail('invalid'); // Error
10
11// UUID-like pattern
12type HexChar = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' |
13 'a' | 'b' | 'c' | 'd' | 'e' | 'f';
14
15// Simplified - actual UUID validation needs runtime check
16type UUIDPattern = `${string}-${string}-${string}-${string}-${string}`;
17
18// Semantic version
19type SemVer = `${number}.${number}.${number}`;
20
21const version: SemVer = '1.2.3'; // OK
22// const bad: SemVer = '1.2'; // ErrorCombining with Mapped Types#
1// Filter keys by pattern
2type FilterByPrefix<T, P extends string> = {
3 [K in keyof T as K extends `${P}${string}` ? K : never]: T[K];
4};
5
6interface Config {
7 apiUrl: string;
8 apiKey: string;
9 dbHost: string;
10 dbPort: number;
11}
12
13type ApiConfig = FilterByPrefix<Config, 'api'>;
14// { apiUrl: string; apiKey: string }
15
16// Remove prefix from keys
17type RemovePrefix<T, P extends string> = {
18 [K in keyof T as K extends `${P}${infer Rest}` ? Uncapitalize<Rest> : K]: T[K];
19};
20
21type CleanApiConfig = RemovePrefix<ApiConfig, 'api'>;
22// { url: string; key: string }
23
24// Add prefix to keys
25type AddPrefix<T, P extends string> = {
26 [K in keyof T as `${P}${Capitalize<string & K>}`]: T[K];
27};
28
29type PrefixedConfig = AddPrefix<{ host: string; port: number }, 'db'>;
30// { dbHost: string; dbPort: number }Real-World Example#
1// Full-featured API type system
2type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
3
4interface ApiDefinition {
5 method: HttpMethod;
6 path: string;
7 params?: Record<string, string>;
8 body?: unknown;
9 response: unknown;
10}
11
12// Define your API
13interface Api {
14 'GET /users': {
15 response: User[];
16 };
17 'GET /users/:id': {
18 params: { id: string };
19 response: User;
20 };
21 'POST /users': {
22 body: { name: string; email: string };
23 response: User;
24 };
25}
26
27// Extract method and path
28type ApiEndpoint = keyof Api;
29type GetEndpoints = Extract<ApiEndpoint, `GET ${string}`>;
30type PostEndpoints = Extract<ApiEndpoint, `POST ${string}`>;
31
32// Type-safe fetch wrapper
33async function apiCall<E extends ApiEndpoint>(
34 endpoint: E,
35 ...args: Api[E] extends { body: infer B }
36 ? [options: { body: B }]
37 : Api[E] extends { params: infer P }
38 ? [options: { params: P }]
39 : []
40): Promise<Api[E]['response']> {
41 // Implementation
42}
43
44// Usage
45const users = await apiCall('GET /users');
46const user = await apiCall('GET /users/:id', { params: { id: '123' } });
47const newUser = await apiCall('POST /users', {
48 body: { name: 'John', email: 'john@example.com' }
49});Best Practices#
Pattern Design:
✓ Keep patterns readable
✓ Use meaningful type names
✓ Document complex patterns
✓ Test with edge cases
Performance:
✓ Limit union expansion
✓ Avoid deeply nested inference
✓ Use simpler alternatives when possible
✓ Watch compilation times
Readability:
✓ Break complex types into parts
✓ Use type aliases
✓ Add comments for inference
✓ Consider runtime alternatives
Avoid:
✗ Overly complex patterns
✗ Massive union expansions
✗ Hard-to-debug type errors
✗ Patterns without runtime validation
Conclusion#
Template literal types enable powerful string manipulation at the type level. Use them for generating event handlers, CSS utility types, API endpoints, and route parameters. Combine with mapped types and conditional types for sophisticated type transformations. Remember that complex patterns can slow down compilation and produce hard-to-read error messages, so balance type safety with maintainability.