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 TypeEffectExample InputResult
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

Leave a Comment