Back to Blog
TypeScriptTemplate LiteralsTypesAdvanced

TypeScript Template Literal Types Guide

Master TypeScript template literal types for powerful string type manipulation.

B
Bootspring Team
Engineering
July 23, 2018
8 min read

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'; // Error

Combining 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.

Share this article

Help spread the word about Bootspring