Template literal types
Template literal types bring string-template syntax to the type level: just as `Hello, ${name}`
builds a string from parts at runtime, `Hello, ${T}` builds a type from type parts at
compile time. They let you describe and construct string types with structure — an event name, a CSS
property, a route path, an API endpoint — with the compiler checking the string's shape, not just that
it is "a string". This is TypeScript doing something almost no other language's type system can.
The syntax
A template literal type uses backticks and ${}, with types in the interpolation:
type Greeting = `Hello, ${string}`;
const a: Greeting = "Hello, Kavita"; // fine — matches the pattern
const b: Greeting = "Hi, Kavita"; // error: not assignable to `Hello, ${string}`
const c: Greeting = "Hello, "; // fine — ${string} can be empty
`Hello, ${string}` is the type of any string that starts with "Hello, ". TypeScript checks the
string against the pattern — "Hi, Kavita" does not start with "Hello, ", so it is rejected. You have
gone from "any string" to "a string with this specific shape", checked at compile time.
Building types from literal unions
The real power comes from interpolating literal unions — TypeScript expands the template over every combination:
type Color = "red" | "green" | "blue";
type Shade = "light" | "dark";
type ColorShade = `${Shade}-${Color}`;
// "light-red" | "light-green" | "light-blue" | "dark-red" | "dark-green" | "dark-blue"
`${Shade}-${Color}` produces every combination of a shade and a colour — six literal types,
generated automatically. Add a colour to Color and all its combinations appear. This is how you type
things like CSS class names, BEM modifiers, or any structured string built from known parts, with the
compiler ensuring only valid combinations are used:
type Route = `/${"users" | "orders" | "products"}/${string}`;
const r1: Route = "/users/123"; // fine
const r2: Route = "/comments/5"; // error: "comments" is not one of the allowed segments
Route describes a URL path whose first segment must be one of three known values — a typo like
/comments/5 is a compile error. This is genuinely useful for typed routing and APIs.
The intrinsic string manipulators
TypeScript ships four built-in types that transform string literals at the type level —
Uppercase, Lowercase, Capitalize, Uncapitalize:
type A = Uppercase<"pune">; // "PUNE"
type B = Lowercase<"PUNE">; // "pune"
type C = Capitalize<"pune">; // "Pune"
type D = Uncapitalize<"Pune">; // "pune"
These operate on the types, not values, and they combine with template literals to transform strings
structurally. The classic use is generating method names from property names — the Getters example
from the mapped-types lesson, now fully explained:
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface User { name: string; age: number; }
type UserGetters = Getters<User>;
// { getName: () => string; getAge: () => number }
`get${Capitalize<string & K>}` takes each key (name), capitalises it (Name), and prefixes
get (getName) — all in the type. This is how a library generates a getter/setter interface, or event
handler names (onClick, onHover) from an events type, entirely in the type system.
Extracting parts with infer
Template literal types compose with infer (last lesson) to parse strings at the type level —
pulling structured pieces out of a string literal type:
type ExtractId<T> = T extends `user_${infer Id}` ? Id : never;
type A = ExtractId<"user_123">; // "123" — captured the part after "user_"
type B = ExtractId<"order_5">; // never — does not match the pattern
T extends `user_${infer Id}` matches a string of the form user_something and captures the
something. This is type-level string parsing — you can split, extract prefixes, and decompose string
types. Libraries use it to derive typed parameters from route patterns (/users/:id →
{ id: string }), which is a genuinely magical-feeling feature when you first meet it.
When to use them — and the honest caution
Template literal types are powerful and, like conditional types, mostly something you read rather than write. Their legitimate everyday uses in application code:
- Constraining a string's shape — an event name that must be
on${Capitalize<EventName>}, a route, a prefixed key, a CSS value. When a string must follow a pattern, this checks it. - Deriving related string types — method names from properties, class names from parts.
The caution is the same as the whole module's: do not build elaborate type-level string machinery for
its own sake. A codebase where simple things are typed with baroque template-literal-and-conditional
gymnastics is harder to work in, not safer — the over-engineering the design-patterns module warns
against, at the type level. Reach for a template literal type when a string genuinely has a shape you
want checked, or when deriving related string types keeps things in sync. Otherwise a plain literal
union ("onClick" | "onHover") is clearer than generating the same union with a template. Power is for
when you need it; clarity is always.
Check your work
What a template literal type is. Type-level string templates — `Hello, ${string}` — describing
a string's shape, checked at compile time.
What interpolating a literal union does. Expands over every combination — `${Shade}-${Color}`
generates all shade-colour pairs.
A practical use. Constraining structured strings — routes, class names, prefixed keys — so a typo in the shape is a compile error.
The four intrinsic manipulators. Uppercase, Lowercase, Capitalize, Uncapitalize — transform
string literals at the type level.
How method names are generated. Key remapping + `get${Capitalize<K>}` — the Getters
pattern.
Composing with infer. T extends `user_${infer Id}` parses a string type, capturing a part —
type-level string parsing (typed routes).
The everyday uses. Constraining a string's shape, and deriving related string types in sync.
The caution. Use them where a string genuinely has a shape worth checking; do not build elaborate type-level string machinery for its own sake — a plain literal union is often clearer.
Practice
- Write
type Greeting = `Hello, ${string}`and assign a matching and non-matching string. Read the error. - Build a
`${Shade}-${Color}`type from two literal unions and confirm all combinations exist. - Write a
Routetemplate type and assign a valid and an invalid path. - Use
Uppercase,Capitalize, and the others on string literals and confirm the results. - Build a
Getters<T>with key remapping and template literals; confirmnamebecomesgetName. - Write
ExtractId<T> = T extends `user_${infer Id}` ? Id : neverand extract the id from"user_123". - Find a place you would use a template literal type, and one where a plain literal union is clearer. Justify each.
Official documentation
- TypeScript — Template Literal Types — The feature and the intrinsic manipulators.
- TypeScript — Intrinsic String Manipulation Types —
Uppercaseand friends. - TypeScript — Inference with template literals — Parsing with
infer.
Next: building your own utility types, and when to stop.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship