TypeScript — Template Literal Types
Template literal types (TypeScript 4.1+) bring the power of JavaScript template literals to the type system. They let you construct string types from other types, manipulate string unions, and build type-safe APIs that enforce naming conventions, event names, CSS properties, and more.
Template literal types use backtick syntax just like JavaScript template literals, but operate at the type level. You embed types inside ${} expressions to construct new string types.
| 1 | // Basic template literal type |
| 2 | type Greeting = `hello ${string}`; |
| 3 | |
| 4 | const valid: Greeting = "hello world"; // OK |
| 5 | const invalid: Greeting = "hi world"; // Error |
| 6 | |
| 7 | // Combining literal and generic types |
| 8 | type EventName<T extends string> = `on${Capitalize<T>}`; |
| 9 | |
| 10 | type ClickEvent = EventName<"click">; // "onClick" |
| 11 | type FocusEvent = EventName<"focus">; // "onFocus" |
| 12 | |
| 13 | // Template literals with multiple interpolations |
| 14 | type ApiRoute<T extends string, U extends string> = `/api/${T}/${U}`; |
| 15 | |
| 16 | type UserPosts = ApiRoute<"users", "posts">; // "/api/users/posts" |
| 17 | type PostComments = ApiRoute<"posts", "comments">; // "/api/posts/comments" |
| 18 | |
| 19 | // Union distribution across template literals |
| 20 | type Color = "red" | "blue" | "green"; |
| 21 | type Size = "sm" | "md" | "lg"; |
| 22 | |
| 23 | type ColorSize = `${Color}-${Size}`; |
| 24 | // "red-sm" | "red-md" | "red-lg" | "blue-sm" | ... | "green-lg" |
| 25 | |
| 26 | // Practical: CSS property names |
| 27 | type CSSUnit = "px" | "rem" | "em" | "vh" | "vw" | "%"; |
| 28 | type CSSValue = `${number}${CSSUnit}`; |
| 29 | |
| 30 | const width: CSSValue = "100px"; // OK |
| 31 | const height: CSSValue = "50vh"; // OK |
| 32 | const bad: CSSValue = "100"; // Error |
When you interpolate a union type into a template literal, TypeScript distributes over the union, producing the Cartesian product of all combinations. This is one of the most powerful features for building exhaustive string types.
| 1 | // Cartesian product of two unions |
| 2 | type Horizontal = "left" | "right"; |
| 3 | type Vertical = "top" | "bottom"; |
| 4 | |
| 5 | type Corner = `${Vertical}-${Horizontal}`; |
| 6 | // "top-left" | "top-right" | "bottom-left" | "bottom-right" |
| 7 | |
| 8 | // Nested unions distribute fully |
| 9 | type Direction = "n" | "s" | "e" | "w"; |
| 10 | type Distance = "1" | "2" | "3"; |
| 11 | |
| 12 | type Move = `${Direction}${Distance}`; |
| 13 | // "n1" | "n2" | "n3" | "s1" | "s2" | "s3" | "e1" | "e2" | "e3" | "w1" | "w2" | "w3" |
| 14 | |
| 15 | // Prefix pattern |
| 16 | type Prefix = "data" | "aria"; |
| 17 | type PropName<T extends string> = `${Prefix}-${T}`; |
| 18 | |
| 19 | type DataLabel = PropName<"label">; // "data-label" |
| 20 | type AriaHidden = PropName<"hidden">; // "aria-hidden" |
| 21 | |
| 22 | // Combining with conditional types |
| 23 | type GetRoutes<T extends string> = T extends `${infer _Start}/${infer _End}` |
| 24 | ? "nested" |
| 25 | : "flat"; |
| 26 | |
| 27 | type R1 = GetRoutes<"users/123">; // "nested" |
| 28 | type R2 = GetRoutes<"health">; // "flat" |
best practice
TypeScript provides four built-in intrinsic types for string manipulation. These operate on individual string literal types and are essential for building ergonomic template literal type patterns.
| 1 | // Uppercase — transforms literal to uppercase |
| 2 | type Upper = Uppercase<"hello">; // "HELLO" |
| 3 | |
| 4 | // Lowercase — transforms literal to lowercase |
| 5 | type Lower = Lowercase<"HELLO">; // "hello" |
| 6 | |
| 7 | // Capitalize — capitalizes the first letter |
| 8 | type Cap = Capitalize<"hello">; // "Hello" |
| 9 | |
| 10 | // Uncapitalize — lowercases the first letter |
| 11 | type Uncap = Uncapitalize<"Hello">; // "hello" |
| 12 | |
| 13 | // Combine with template literals for event naming |
| 14 | type ToEventName<T extends string> = `on${Capitalize<T>}`; |
| 15 | |
| 16 | type Click = ToEventName<"click">; // "onClick" |
| 17 | type Change = ToEventName<"change">; // "onChange" |
| 18 | |
| 19 | // Camel case helper |
| 20 | type CamelCase<S extends string> = S extends `${infer Head}-${infer Tail}` |
| 21 | ? `${Head}${CamelCase<Capitalize<Tail>>}` |
| 22 | : S; |
| 23 | |
| 24 | type KebabToCamel = CamelCase<"background-color">; // "backgroundColor" |
| 25 | type KebabToCamel2 = CamelCase<"border-top-width">; // "borderTopWidth" |
| 26 | type KebabToCamel3 = CamelCase<"margin-left">; // "marginLeft" |
| 27 | |
| 28 | // Reverse: camel to kebab |
| 29 | type KebabCase<S extends string> = S extends `${infer Head}${infer Tail}` |
| 30 | ? Head extends Uppercase<Head> |
| 31 | ? Head extends Lowercase<Head> |
| 32 | ? `${Head}${KebabCase<Tail>}` |
| 33 | : `-${Lowercase<Head>}${KebabCase<Tail>}` |
| 34 | : `${Head}${KebabCase<Tail>}` |
| 35 | : S; |
| 36 | |
| 37 | type CamelToKebab = KebabCase<"backgroundColor">; // "background-color" |
| 38 | type CamelToKebab2 = KebabCase<"marginTop">; // "margin-top" |
info
Template literal types support infer for pattern matching on strings. This lets you extract substrings, parse string formats, and build type-safe parsers.
| 1 | // Extract the ID from "/users/:id/posts/:postId" |
| 2 | type ExtractId<T extends string> = |
| 3 | T extends `/users/${infer Id}/posts/${infer PostId}` |
| 4 | ? { userId: Id; postId: PostId } |
| 5 | : never; |
| 6 | |
| 7 | type Result = ExtractId<"/users/42/posts/99">; |
| 8 | // { userId: "42"; postId: "99" } |
| 9 | |
| 10 | // Match a specific prefix |
| 11 | type HasPrefix<T extends string> = |
| 12 | T extends `api-${infer _Rest}` ? true : false; |
| 13 | |
| 14 | type A = HasPrefix<"api-users">; // true |
| 15 | type B = HasPrefix<"web-users">; // false |
| 16 | |
| 17 | // Extract path segments |
| 18 | type Split<S extends string, D extends string> = |
| 19 | S extends `${infer Head}${D}${infer Tail}` |
| 20 | ? [Head, ...Split<Tail, D>] |
| 21 | : [S]; |
| 22 | |
| 23 | type Segments = Split<"a/b/c", "/">; // ["a", "b", "c"] |
| 24 | |
| 25 | // Parse query parameters from a string |
| 26 | type ParseQuery<T extends string> = |
| 27 | T extends `${infer Key}=${infer Value}&${infer Rest}` |
| 28 | ? { [K in Key]: Value } & ParseQuery<Rest> |
| 29 | : T extends `${infer Key}=${infer Value}` |
| 30 | ? { [K in Key]: Value } |
| 31 | : {}; |
| 32 | |
| 33 | type Params = ParseQuery<"name=alice&age=30">; |
| 34 | // { name: "alice" } & { age: "30" } |
| 35 | |
| 36 | // Validate email-like pattern |
| 37 | type IsEmail<T extends string> = |
| 38 | T extends `${infer User}@${infer Domain}.${infer TLD}` |
| 39 | ? true |
| 40 | : false; |
| 41 | |
| 42 | type E1 = IsEmail<"user@example.com">; // true |
| 43 | type E2 = IsEmail<"not-an-email">; // false |
Template literal types shine in real-world APIs where string shapes matter: CSS-in-JS prop builders, event handler registries, typed API route definitions, and configuration key access.
| 1 | // 1. CSS prop builder — type-safe margin/padding props |
| 2 | type Direction = "top" | "right" | "bottom" | "left"; |
| 3 | type Spacing = "0" | "1" | "2" | "3" | "4" | "6" | "8"; |
| 4 | |
| 5 | type MarginProps = { |
| 6 | [K in Direction as `margin-${K}`]: `${Spacing}px` | `${Spacing}rem`; |
| 7 | }; |
| 8 | |
| 9 | const m: MarginProps = { |
| 10 | "margin-top": "4px", // OK |
| 11 | "margin-left": "8rem", // OK |
| 12 | "margin-bottom": "2px", // OK |
| 13 | "margin-right": "0px", // OK |
| 14 | }; |
| 15 | |
| 16 | // 2. Typed event handlers |
| 17 | type DOMEvent = "click" | "change" | "submit" | "keydown" | "focus"; |
| 18 | type EventHandlerProps = { |
| 19 | [E in DOMEvent as `on${Capitalize<E>}`]: (e: Event) => void; |
| 20 | }; |
| 21 | |
| 22 | const handlers: EventHandlerProps = { |
| 23 | onClick: (e) => console.log("clicked"), |
| 24 | onChange: (e) => console.log("changed"), |
| 25 | onSubmit: (e) => console.log("submitted"), |
| 26 | onKeydown: (e) => console.log("key pressed"), |
| 27 | onFocus: (e) => console.log("focused"), |
| 28 | }; |
| 29 | |
| 30 | // 3. API endpoint type builder |
| 31 | type Method = "GET" | "POST" | "PUT" | "DELETE"; |
| 32 | type Resource = "users" | "posts" | "comments"; |
| 33 | |
| 34 | type Endpoint = `/api/${Lowercase<Resource>}`; |
| 35 | type TypedFetch<T extends Endpoint> = { |
| 36 | url: T; |
| 37 | method: Method; |
| 38 | }; |
| 39 | |
| 40 | const req: TypedFetch<"/api/users"> = { |
| 41 | url: "/api/users", |
| 42 | method: "GET", |
| 43 | }; |
| 44 | |
| 45 | // 4. Configuration key path access |
| 46 | type DotPrefix<T extends string> = T extends "" ? "" : `.${T}`; |
| 47 | type DotJoin<A extends string, B extends string> = |
| 48 | `${A}${DotPrefix<B>}`; |
| 49 | |
| 50 | type Config = { |
| 51 | database: { host: string; port: number }; |
| 52 | cache: { ttl: number }; |
| 53 | }; |
| 54 | |
| 55 | type ConfigPath<K extends string, V> = |
| 56 | V extends object |
| 57 | ? { [P in keyof V & string]: DotJoin<K, P> } & { |
| 58 | [P in keyof V & string]: ConfigPath<DotJoin<K, P>, V[P]>; |
| 59 | }[keyof V & string] |
| 60 | : K; |
| 61 | |
| 62 | type Paths = ConfigPath<"", Config>; |
| 63 | // ".database" | ".database.host" | ".database.port" | ".cache" | ".cache.ttl" |
best practice
Template literal types are powerful but can quickly become unwieldy. Follow these guidelines to keep your types maintainable and your editor performant.
Keep unions small. Cartesian products grow fast — 5 × 5 × 5 = 125 types. If you exceed ~50 combinations, reconsider the design or use generic string interpolation.
Use intrinsic types for casing. Capitalize and Uncapitalize are faster and clearer than manual case conversion with conditional types.
Prefer constraints over unions. Use ${infer} and conditional types for extraction, not just union matching. This works with any string, not just known values.
Name your types clearly. Recursive template literal types are hard to debug. Extract intermediate types with meaningful names so errors are readable.
Beware circular types. Recursive template literal types can cause infinite instantiation errors. Always ensure the recursion terminates with a base case.