Back to Blog
TypeScriptTypesTemplate LiteralsAdvanced

TypeScript Template Literal Types

Master TypeScript template literal types for string manipulation at the type level.

B
Bootspring Team
Engineering
March 30, 2020
6 min read

Template literal types combine literal types with string interpolation. Here's how to use them.

Basic Syntax#

1// Simple template literal type 2type Greeting = `Hello, ${string}!`; 3 4const g1: Greeting = 'Hello, World!'; // OK 5const g2: Greeting = 'Hello, Alice!'; // OK 6// const g3: Greeting = 'Hi, World!'; // Error 7 8// With union types 9type Color = 'red' | 'green' | 'blue'; 10type Size = 'small' | 'medium' | 'large'; 11 12type ColorSize = `${Color}-${Size}`; 13// 'red-small' | 'red-medium' | 'red-large' | 14// 'green-small' | 'green-medium' | 'green-large' | 15// 'blue-small' | 'blue-medium' | 'blue-large' 16 17// Multiple placeholders 18type Coordinate = `${number},${number}`; 19const coord: Coordinate = '10,20'; // OK

Event Naming#

1// Event handler type 2type EventName = 'click' | 'focus' | 'blur' | 'change'; 3type HandlerName = `on${Capitalize<EventName>}`; 4// 'onClick' | 'onFocus' | 'onBlur' | 'onChange' 5 6// Prop types for events 7type EventHandlers = { 8 [K in EventName as `on${Capitalize<K>}`]: (event: Event) => void; 9}; 10// { 11// onClick: (event: Event) => void; 12// onFocus: (event: Event) => void; 13// onBlur: (event: Event) => void; 14// onChange: (event: Event) => void; 15// } 16 17// Add event listener types 18type AddListener<T extends string> = `add${Capitalize<T>}Listener`; 19type RemoveListener<T extends string> = `remove${Capitalize<T>}Listener`; 20 21type ClickListeners = AddListener<'click'>; // 'addClickListener'

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// Combine with template literals 8type HttpMethod = 'get' | 'post' | 'put' | 'delete'; 9type MethodLabel = `HTTP_${Uppercase<HttpMethod>}`; 10// 'HTTP_GET' | 'HTTP_POST' | 'HTTP_PUT' | 'HTTP_DELETE' 11 12// CSS property to camelCase type 13type CSSProperty = 'background-color' | 'font-size' | 'border-radius'; 14// Would need custom type for full kebab-to-camel conversion

Getter and Setter Types#

1interface Person { 2 name: string; 3 age: number; 4 email: string; 5} 6 7// Generate getter names 8type Getters<T> = { 9 [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]; 10}; 11 12type PersonGetters = Getters<Person>; 13// { 14// getName: () => string; 15// getAge: () => number; 16// getEmail: () => string; 17// } 18 19// Generate setter names 20type Setters<T> = { 21 [K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void; 22}; 23 24type PersonSetters = Setters<Person>; 25// { 26// setName: (value: string) => void; 27// setAge: (value: number) => void; 28// setEmail: (value: string) => void; 29// } 30 31// Combined 32type WithAccessors<T> = T & Getters<T> & Setters<T>;

Route Patterns#

1// API route types 2type ApiVersion = 'v1' | 'v2'; 3type Resource = 'users' | 'posts' | 'comments'; 4 5type ApiRoute = `/api/${ApiVersion}/${Resource}`; 6// '/api/v1/users' | '/api/v1/posts' | '/api/v1/comments' | 7// '/api/v2/users' | '/api/v2/posts' | '/api/v2/comments' 8 9// With parameters 10type ResourceRoute = `/api/${Resource}/${number}`; 11 12// Extract route params 13type ExtractParams<T extends string> = 14 T extends `${infer _Start}:${infer Param}/${infer Rest}` 15 ? Param | ExtractParams<`/${Rest}`> 16 : T extends `${infer _Start}:${infer Param}` 17 ? Param 18 : never; 19 20type Params = ExtractParams<'/users/:id/posts/:postId'>; 21// 'id' | 'postId'

CSS Class Utilities#

1// Tailwind-like utility types 2type Spacing = 0 | 1 | 2 | 4 | 8 | 16; 3type Direction = 't' | 'r' | 'b' | 'l' | 'x' | 'y'; 4 5type MarginClass = `m${Direction}-${Spacing}`; 6// 'mt-0' | 'mt-1' | ... | 'my-16' 7 8type PaddingClass = `p${Direction}-${Spacing}`; 9 10// Color utilities 11type ColorName = 'red' | 'blue' | 'green' | 'gray'; 12type ColorShade = 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900; 13 14type TextColor = `text-${ColorName}-${ColorShade}`; 15type BgColor = `bg-${ColorName}-${ColorShade}`; 16 17// Responsive prefixes 18type Breakpoint = 'sm' | 'md' | 'lg' | 'xl'; 19type ResponsiveClass<T extends string> = T | `${Breakpoint}:${T}`; 20 21type ResponsiveMargin = ResponsiveClass<MarginClass>; 22// 'mt-0' | 'sm:mt-0' | 'md:mt-0' | ...

Database Column Types#

1// Table column naming 2type TableName = 'users' | 'posts' | 'comments'; 3type ColumnPrefix<T extends string> = `${T}_`; 4 5type UserColumn = `${ColumnPrefix<'user'>}${string}`; 6const col: UserColumn = 'user_id'; // OK 7 8// Foreign key convention 9type ForeignKey<T extends string> = `${T}_id`; 10 11type UserFK = ForeignKey<'user'>; // 'user_id' 12type PostFK = ForeignKey<'post'>; // 'post_id' 13 14// Timestamp columns 15type TimestampColumn = `${string}_at`; 16const created: TimestampColumn = 'created_at'; // OK 17const updated: TimestampColumn = 'updated_at'; // OK

Pattern Matching#

1// Extract parts of string types 2type ExtractDomain<T extends string> = 3 T extends `${infer _Protocol}://${infer Domain}/${infer _Path}` 4 ? Domain 5 : T extends `${infer _Protocol}://${infer Domain}` 6 ? Domain 7 : never; 8 9type Domain = ExtractDomain<'https://example.com/path'>; 10// 'example.com' 11 12// Parse email 13type ExtractEmailParts<T extends string> = 14 T extends `${infer User}@${infer Domain}` 15 ? { user: User; domain: Domain } 16 : never; 17 18type EmailParts = ExtractEmailParts<'alice@example.com'>; 19// { user: 'alice'; domain: 'example.com' } 20 21// Split string type 22type Split<S extends string, D extends string> = 23 S extends `${infer T}${D}${infer U}` 24 ? [T, ...Split<U, D>] 25 : [S]; 26 27type Parts = Split<'a-b-c', '-'>; 28// ['a', 'b', 'c']

Action Types (Redux-style)#

1// Action type naming 2type Feature = 'user' | 'post' | 'comment'; 3type ActionVerb = 'fetch' | 'create' | 'update' | 'delete'; 4type ActionStatus = 'request' | 'success' | 'failure'; 5 6type ActionType = `${Uppercase<Feature>}_${Uppercase<ActionVerb>}_${Uppercase<ActionStatus>}`; 7// 'USER_FETCH_REQUEST' | 'USER_FETCH_SUCCESS' | ... 8 9// Generate action creators 10type ActionCreator<T extends string> = { 11 type: T; 12 payload?: unknown; 13}; 14 15// Namespace actions 16type NamespacedAction< 17 NS extends string, 18 Action extends string 19> = `[${NS}] ${Action}`; 20 21type UserAction = NamespacedAction<'User', 'Load'>; 22// '[User] Load'

Environment Variables#

1// Env var naming convention 2type EnvPrefix = 'VITE' | 'NEXT' | 'REACT_APP'; 3type EnvVar<P extends EnvPrefix> = `${P}_${Uppercase<string>}`; 4 5type ViteEnv = EnvVar<'VITE'>; 6 7// Type-safe env access 8type EnvConfig = { 9 VITE_API_URL: string; 10 VITE_APP_TITLE: string; 11 VITE_DEBUG: 'true' | 'false'; 12}; 13 14function getEnv<K extends keyof EnvConfig>(key: K): EnvConfig[K] { 15 return process.env[key] as EnvConfig[K]; 16} 17 18const apiUrl = getEnv('VITE_API_URL'); // string

Best Practices#

Patterns: ✓ Use for event handler names ✓ Generate accessor methods ✓ Type API routes ✓ CSS utility types String Manipulation: ✓ Uppercase/Lowercase for conventions ✓ Capitalize for camelCase ✓ Combine with key remapping ✓ Pattern matching with infer Performance: ✓ Limit union combinations ✓ Avoid deep recursion ✓ Cache complex types ✓ Use type aliases Avoid: ✗ Extremely large unions ✗ Complex nested templates ✗ Runtime string building ✗ Over-engineering simple cases

Conclusion#

Template literal types enable powerful string manipulation at the type level. Use them for event naming conventions, accessor generation, route typing, and CSS utilities. Combine with Uppercase, Lowercase, and Capitalize for consistent naming. Be mindful of union explosion when combining multiple union types in templates.

Share this article

Help spread the word about Bootspring