Template Literal Types
Combine and transform string types like a Hyderabad signboard painter
Template literal types let you create NEW string literal types by COMBINING and TRANSFORMING existing string literal types, using the same backtick syntax as JavaScript template literals. It's TypeScript's string manipulation at the TYPE level.
Think of the auto rickshaw signboard painter in Hyderabad. The painter has FIXED text templates: "HITEC City" on one side, "Secunderabad" on the other, "SHARED" or "PRIVATE" on top. He combines these like: `${Route} → ${Destination}`. Depending on which route and destination you pick, the painter creates a SPECIFIC sign: "SHARED → HITEC City", "PRIVATE → Secunderabad". Each combination is a unique string literal type.
TypeScript does the same — it takes union types of strings and COMBINES every possibility. The signboard painter just multiplied 2 routes × 2 vehicle types = 4 signboards. Template literal types are the type-level signboard painter!
type Mode = "shared" | "private";
type Vehicle = "auto" | "cab";
type Signboard =
`${Mode}-${Vehicle}`;
// "shared-auto" | "shared-cab"
// "private-auto" | "private-cab"
See what happened? Two unions of 2 members each produced 4 possible types — the Cartesian product. This is the core power of template literal types. You define the PATTERN, and TypeScript computes every valid combination. Seedha samjho: you give the template, TypeScript paints every possible signboard!
This feature unlocks patterns that were previously impossible or required messy string manipulation. Type-safe event names, CSS property transformers, route pattern builders — all become clean and automatic with template literal types.
The syntax uses backticks with embedded types — exactly like JavaScript template literals, but at the type level:
type Name = "Hyderabad";
type Greeting =
`Hello ${Name}`;
// "Hello Hyderabad"
When you embed a UNION of string literals, TypeScript computes every combination:
type Route =
`Route: ${"A"|"B"|"C"}`;
// "Route:A"|"Route:B"|"Route:C"
type EventName =
`on${"Click"|"Hover"|"Focus"}`;
// "onClick"|"onHover"|"onFocus"
This is the Cartesian product in action. Two unions multiply their possibilities:
type Combo =
`${"a"|"b"}${"1"|"2"}`;
// "a1" | "a2" | "b1" | "b2"
You can use string in template literals, but it produces a WIDE type — TypeScript can't enumerate all possible strings:
type AnyEmail =
`${string}@${string}.com`;
// Matches any email-like string
type ID = `${string}-${number}`;
// "user-123", "order-456", etc.
One of the most powerful patterns combines template literals with keyof:
type Getter<T> =
`get${Capitalize<
string & keyof T
>}`;
interface User {
name: string;
age: number;
}
type UserGetters = Getter<User>;
// "getName" | "getAge"
This pattern — deriving method names from property names — is used heavily in frameworks like Vue, Angular, and ORM libraries. Template literal types make it type-safe and automatic. Each property key gets transformed into a properly-cased method name.
TypeScript provides 4 intrinsic string manipulation types for use inside template literals. These are built into the compiler — you can't implement them yourself in TypeScript.
1. Uppercase<S> — converts the entire string to uppercase:
type Shout = Uppercase<"hello">;
// "HELLO"
type Code = Uppercase<"ts"|"js">;
// "TS" | "JS"
2. Lowercase<S> — converts the entire string to lowercase:
type Calm = Lowercase<"HELLO">;
// "hello"
type Ext = Lowercase<"TS"|"JS">;
// "ts" | "js"
3. Capitalize<S> — capitalizes the first letter only:
type City = Capitalize<"hyd">;
// "Hyd"
type Keys = Capitalize<"name"|"age">;
// "Name" | "Age"
4. Uncapitalize<S> — lowercases the first letter only:
type Prop =
Uncapitalize<"UserName">;
// "userName"
The most practical pattern is generating getter and setter names:
type GetterName<T extends string> =
`get${Capitalize<T>}`;
type SetterName<T extends string> =
`set${Capitalize<T>}`;
type GetName = GetterName<"name">;
// "getName"
type SetAge = SetterName<"age">;
// "setAge"
Another great pattern — type-safe event handlers:
type EventHandler<
T extends string
> = `on${Capitalize<T>}`;
type Click = EventHandler<"click">;
// "onClick"
type Events =
EventHandler<"click"|"change">;
// "onClick" | "onChange"
These intrinsic types work with unions too — each member gets transformed independently. Capitalize<"hello" | "world"> gives you "Hello" | "World". This makes them perfect for generating consistent naming patterns across your entire codebase.
Template literal types are powerful, but they come with traps that can blow up in your face if you're not careful. Let's walk through the most common ones.
Trap 1: Explosion of combinations. When you combine multiple unions in a template literal, the possibilities multiply FAST:
type P1 = "a" | "b" | "c";
type P2 = "d" | "e" | "f";
type P3 = "g" | "h" | "i";
// 3 × 3 × 3 = 27 combos!
type Big = `${P1}-${P2}-${P3}`;
With larger unions — say 10 × 10 × 10 — you hit 1000 combinations. TypeScript has internal limits and will error if you go too far. Keep your unions small and manageable!
Trap 2: string produces dangerously wide types.
type T = `${string}-${string}`;
// Matches almost ANY string
// with a dash. Very loose!
const a: T = "hello-world"; // OK
const b: T = "---"; // Also OK!
Don't use string when you mean specific literals. The wide type provides almost no type safety.
Trap 3: Template literals don't validate at runtime.
type Email =
`${string}@${string}.${string}`;
// This compiles but is garbage!
const bad: Email = "@.";
TypeScript's template literal types describe PATTERNS, but they don't enforce runtime validation. For actual validation, use a library like Zod. Template literals are type-level only!
Trap 4: Numeric literals auto-convert. `${number}` works because TypeScript auto-converts number to its string form. And `${1 | 2 | 3}` produces "1" | "2" | "3" — numeric literals become string literals automatically. This is convenient but can be surprising.
Trap 5: Circular template literals. Types that reference themselves in template literals can create infinite loops. TypeScript has recursion limits and will error. Don't try to build self-referencing string types!
Time for the rapid-fire cheatsheet. Pin this to your desk or paint it on your auto signboard — whatever works!
Template Literal Syntax:
// Basic syntax
type Basic = `text${Type}text`;
// Unions → all combos
type Uni = `${A|B}-${C|D}`;
// string → wide pattern
type Wide = `${string}@mail.com`;
Intrinsic String Types:
Uppercase<"hello"> // "HELLO"
Lowercase<"HELLO"> // "hello"
Capitalize<"hello"> // "Hello"
Uncapitalize<"Hello"> // "hello"
Common Patterns:
// Getter/Setter names
type Getter<T> =
`get${Capitalize<T>}`;
// Event handlers
type OnEvent<T> =
`on${Capitalize<T>}`;
// Route patterns
type Route<S> = `/api/${S}`;
// ID formats
type ID = `${string}-${number}`;
Key Rules:
- Backtick syntax — same as JS template literals, but at type level
- Unions create Cartesian products — combinations multiply
stringproduces wide pattern types — use sparingly- Intrinsic types transform casing — Uppercase, Lowercase, Capitalize, Uncapitalize
- No runtime validation — type-level only, use Zod for runtime
- Watch for combination explosion — keep unions small
The golden rule: "Template literal types are the auto signboard painter — combine fixed text with variable parts to create exact string types. But don't paint 1000 signboards, bhai — keep it manageable!"
Key Takeaways
- Template literal types combine string types using backtick syntax at the type level
- Unions in template literals produce Cartesian products — all possible combinations
- Four intrinsic types: Uppercase, Lowercase, Capitalize, Uncapitalize
- Using `string` in template literals creates wide, permissive pattern types
- Template literals are type-level only — no runtime validation without Zod etc.
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login