TSadvancedts-evergreen

Template literal types

String types you can compute.

Template literal types apply backtick interpolation at the type level β€” ` on${Capitalize<E>} ` β€” letting the compiler build, constrain, and even parse string types from other types instead of treating every string the same.

Strings as structure, not soup

A plain string type says nothing about a value's shape. A template literal type uses the same backtick-and-${} syntax you know from runtime strings, but the placeholders hold types, not values. type Greeting = `Hello, ${string}!` matches "Hello, Ada!" and "Hello, world!" but rejects "Goodbye" β€” the literal parts must line up exactly. Swap string for a union or a generic parameter and the compiler enumerates or constrains the result for you.

The four intrinsic string types

TypeScript ships four built-in helpers that only make sense inside template literals: Uppercase<S>, Lowercase<S>, Capitalize<S>, and Uncapitalize<S>. They transform the literal string type character-wise at compile time. Pair them with interpolation to derive things like event-handler names β€” `on${Capitalize<E>}` turns "click" into "onClick" β€” without writing a single runtime line.

Distribution turns one pattern into many

When a placeholder is a union, the template distributes over every member and produces the cross-product. `${'sm' | 'lg'}-${'red' | 'blue'}` expands to "sm-red" | "sm-blue" | "lg-red" | "lg-blue". This is how a handful of literal unions generate a complete, exhaustive set of valid class names, CSS units, or route keys β€” the type system does the combinatorics so a typo can't slip through.

Parsing strings back apart with infer

Template literals also work in reverse. Inside a conditional type, a placeholder can be infer X, capturing the matching slice as a new type. T extends `/${infer Param}` ? Param : never pulls the segment after the slash out of a route string. Recurse on the tail and you can extract every :param from "/users/:id/posts/:postId" into a typed object β€” a tiny parser that runs entirely in the checker.

Try it 5 examples

A greeting type with a literal placeholder

TSintro
Press Run to execute

greet('Ada') returns "Hello, Ada!" and greet('world') returns "Hello, world!" β€” the same literal shape the Greeting<T> type encodes, so the runtime builder's return type is the template literal type itself, not a loose string.