TypeScript Template Literal Types
Template literal types build new string types by combining other types inside backtick syntax — the same backtick syntax used in JavaScript template strings, but applied at the type level. They allow the creation of precise string types that represent patterns, combinations, or variations of strings, all checked at compile time.
What Are Template Literal Types?
JavaScript template string (runtime value):
let greeting = `Hello, ${name}!`; ← Produces a string value
TypeScript template literal type (compile-time type):
type Greeting = `Hello, ${string}!`; ← Produces a string type
Any string matching the pattern is valid:
let g: Greeting = "Hello, Priya!"; ✓
let g: Greeting = "Hello, World!"; ✓
let g: Greeting = "Hi, Priya!"; ✗ ← Doesn't match pattern
Basic Template Literal Type
type EventName = `on${string}`;
let click: EventName = "onClick"; // Valid — starts with "on"
let submit: EventName = "onSubmit"; // Valid
let hover: EventName = "onHover"; // Valid
let invalid: EventName = "click"; // Error — does not start with "on"
Combining String Unions
When a union type appears inside a template literal type, TypeScript generates all possible combinations automatically. This is one of the most powerful features of template literal types.
type Color = "red" | "green" | "blue";
type Shade = "light" | "dark";
type ColorShade = `${Shade}-${Color}`;
// Result: "light-red" | "light-green" | "light-blue"
// | "dark-red" | "dark-green" | "dark-blue"
let theme: ColorShade = "dark-blue"; // Valid
let theme2: ColorShade = "light-green"; // Valid
let theme3: ColorShade = "medium-red"; // Error — "medium" not in Shade
CSS Property Type Example
type Side = "top" | "right" | "bottom" | "left";
type CSSSpacing = `padding-${Side}` | `margin-${Side}`;
// Generates: "padding-top" | "padding-right" | "padding-bottom" | "padding-left"
// | "margin-top" | "margin-right" | "margin-bottom" | "margin-left"
let cssProp: CSSSpacing = "padding-top"; // Valid
let cssProp2: CSSSpacing = "margin-left"; // Valid
let cssProp3: CSSSpacing = "padding-center"; // Error
Template Literal Types with Interfaces
type EventType = "click" | "focus" | "blur" | "change";
type ElementId = string;
type EventHandler = `handle${Capitalize<EventType>}`;
// "handleClick" | "handleFocus" | "handleBlur" | "handleChange"
interface FormHandlers {
handleClick(): void;
handleFocus(): void;
handleBlur(): void;
handleChange(): void;
}
Built-In String Manipulation Types
TypeScript includes four utility types specifically for transforming string types:
| Utility Type | Effect | Example Input | Result |
|---|---|---|---|
Uppercase<S> | All characters uppercase | "hello" | "HELLO" |
Lowercase<S> | All characters lowercase | "WORLD" | "world" |
Capitalize<S> | First character uppercase | "typescript" | "Typescript" |
Uncapitalize<S> | First character lowercase | "TypeScript" | "typeScript" |
type Status = "active" | "inactive" | "pending"; type UpperStatus = Uppercase<Status>; // "ACTIVE" | "INACTIVE" | "PENDING" type CapStatus = Capitalize<Status>; // "Active" | "Inactive" | "Pending" let s: UpperStatus = "ACTIVE"; // Valid let s2: CapStatus = "Pending"; // Valid
Getter and Setter Type Generation
Template literal types automatically generate getter and setter method names from a set of property names.
type PropName = "name" | "age" | "email";
type Getters = `get${Capitalize<PropName>}`;
// "getName" | "getAge" | "getEmail"
type Setters = `set${Capitalize<PropName>}`;
// "setName" | "setAge" | "setEmail"
type UserAccessors = Getters | Setters;
interface UserAPI {
getName(): string;
getAge(): number;
getEmail(): string;
setName(v: string): void;
setAge(v: number): void;
setEmail(v: string): void;
}
Dynamic Object Key Types
type Prefixed<T extends string> = `data_${T}`;
type UserKeys = Prefixed<"id" | "name" | "email">;
// "data_id" | "data_name" | "data_email"
type DataMap = {
[K in UserKeys]: string;
};
let dataRecord: DataMap = {
data_id: "1001",
data_name: "Santosh",
data_email: "santosh@mail.com"
};
Route Path Type Pattern
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type ApiPath = "/users" | "/courses" | "/orders";
type ApiRoute = `${HttpMethod} ${ApiPath}`;
// "GET /users" | "GET /courses" | "GET /orders"
// "POST /users" | "POST /courses" | "POST /orders"
// ... (12 combinations total)
function callApi(route: ApiRoute): void {
console.log("Calling: " + route);
}
callApi("GET /courses"); // Valid
callApi("POST /users"); // Valid
callApi("PATCH /courses"); // Error — PATCH not in HttpMethod
Practical Example — Event System
type Entity = "user" | "course" | "order";
type Action = "created" | "updated" | "deleted";
type AppEvent = `${Entity}:${Action}`;
// "user:created" | "user:updated" | "user:deleted"
// "course:created" | "course:updated" | "course:deleted"
// "order:created" | "order:updated" | "order:deleted"
type EventHandler = (eventName: AppEvent, data: unknown) => void;
function on(event: AppEvent, handler: EventHandler): void {
console.log("Registered handler for: " + event);
}
function emit(event: AppEvent, data: unknown): void {
console.log("Event fired: " + event);
}
on("user:created", (e, data) => console.log("User created:", data));
on("course:updated", (e, data) => console.log("Course updated:", data));
emit("user:created", { id: 1, name: "Harish" });
// Event fired: user:created
emit("order:deleted", { orderId: 5001 });
// Event fired: order:deleted
emit("payment:failed", {}); // Error — "payment" not in Entity
