TypeScript template literal types: type-safe string manipulation
← Back
April 4, 2026NodeJS6 min read

TypeScript template literal types: type-safe string manipulation

Published April 4, 20266 min read

I was building an event system where event names followed a pattern like "user:created", "order:cancelled", "payment:failed". I wanted type safety on these strings without manually listing every combination. Template literal types made it possible to derive the full union automatically from the component parts. Here is the pattern and the real-world use cases that make it worth learning.

Basic template literal types

typescript
type Noun = 'user' | 'order' | 'payment';
type Verb = 'created' | 'updated' | 'deleted' | 'failed';

// Generates all combinations: "user:created", "user:updated", ..., "payment:failed"
type EventName = `${Noun}:${Verb}`;

// Type-safe event system
type EventHandler = (data: T) => void;
type EventMap = Record;

function on(event: EventName, handler: EventHandler): void {
  // ...
}

on("user:created", handleUserCreated);   // OK
on("payment:failed", handlePaymentFail); // OK
on("user:launched", handler);            // Error! "launched" not in Verb

CSS property generation

typescript
type Side = 'top' | 'right' | 'bottom' | 'left';
type Size = 'sm' | 'md' | 'lg' | 'xl';

// Tailwind-like spacing classes
type SpacingClass = `m${'t' | 'r' | 'b' | 'l' | 'x' | 'y'}-${Size}`;
// "mt-sm" | "mt-md" | "mt-lg" | "mt-xl" | "mr-sm" | ...

type PaddingClass = `p${'t' | 'r' | 'b' | 'l' | 'x' | 'y'}-${Size}`;

type ClassName = SpacingClass | PaddingClass | 'flex' | 'block' | 'hidden';

function cx(...classes: ClassName[]): string {
  return classes.join(' ');
}

cx('mt-lg', 'px-md', 'flex');  // Type-safe
cx('mt-huge');                  // Error! "huge" not in Size

Automatic getter/setter method names

typescript
type Getter = `get${Capitalize}`;
type Setter = `set${Capitalize}`;

type ModelProps = 'name' | 'email' | 'role';

// Generates: { getName, setName, getEmail, setEmail, getRole, setRole }
type ModelMethods = {
  [K in ModelProps as Getter]: () => string;
} & {
  [K in ModelProps as Setter]: (value: string) => void;
};

function createModel(): ModelMethods {
  const state: Record = {};
  return new Proxy({} as ModelMethods, {
    get(_, prop: string) {
      if (prop.startsWith('get')) {
        const key = prop.slice(3).toLowerCase();
        return () => state[key];
      }
      if (prop.startsWith('set')) {
        const key = prop.slice(3).toLowerCase();
        return (value: string) => { state[key] = value; };
      }
    }
  });
}

const model = createModel();
model.setName("Alice");
model.getName();  // "Alice"
model.setFoo("x"); // Error! setFoo not in ModelMethods

Type-safe API client paths

typescript
type ApiVersion = 'v1' | 'v2';
type Resource = 'users' | 'orders' | 'products';
type ApiPath = `/api/${ApiVersion}/${Resource}`;
// "/api/v1/users" | "/api/v1/orders" | "/api/v1/products" | "/api/v2/..."

// More complex: with ID params
type ResourcePath = `/api/${ApiVersion}/${Resource}` | `/api/${ApiVersion}/${Resource}/${string}`;

async function apiCall(path: ResourcePath, options?: RequestInit): Promise {
  const response = await fetch(path, options);
  return response.json();
}

apiCall('/api/v1/users');
apiCall('/api/v2/orders/123');
apiCall('/api/v3/users');  // Error! v3 not in ApiVersion

String manipulation utilities

typescript
// Built-in template literal type utilities
type Upper = Uppercase<'hello world'>;     // "HELLO WORLD"
type Lower = Lowercase<'HELLO WORLD'>;     // "hello world"
type Camel = Capitalize<'hello world'>;    // "Hello world"
type Uncamel = Uncapitalize<'HelloWorld'>; // "helloWorld"

// Useful for code generation
type CamelCase = 
  S extends `${infer Head}_${infer Tail}`
    ? `${Head}${Capitalize>}`
    : S;

type DBColumn = 'user_id' | 'created_at' | 'order_count';
type CamelColumns = { [K in DBColumn as CamelCase]: string };
// { userId: string; createdAt: string; orderCount: string }

Template literal types are one of those TypeScript features that look obscure until you hit the right use case, and then they feel essential. Event systems, CSS class utilities, API paths, and code generation patterns all benefit from the ability to derive exact string unions from component types rather than manually maintaining long union lists.

Share this
← All Posts6 min read