David John Thammineni
TypeScript21 Oct 20212 min read

Template Literal Types: Typing the Strings You Compose

Template Literal Types: Typing the Strings You Compose

Template literal types let the compiler understand strings you build by composition. The feature invites party tricks, but a few patterns are genuinely load-bearing in application code.

Typed event names

type Entity = "user" | "project" | "invoice";
type Action = "created" | "updated" | "deleted";

type EventName = `${Entity}:${Action}`;
// "user:created" | "user:updated" | ... 9 combinations

function emit(event: EventName, payload: unknown) { /* ... */ }

emit("invoice:created", data);  // ok
emit("invoice:destroyed", data); // compile error

Nine valid names, zero maintained by hand. Add "payment" to Entity and the event space grows correctly everywhere.

Typed route builders

type Route = "/users/:id" | "/projects/:id/tasks/:taskId";

type Params<T extends string> =
  T extends `${string}:${infer P}/${infer Rest}`
    ? P | Params<`/${Rest}`>
    : T extends `${string}:${infer P}`
      ? P
      : never;

function buildPath<T extends Route>(
  route: T,
  params: Record<Params<T>, string>
): string {
  return route.replace(/:(\w+)/g, (_, k) => params[k as Params<T>]);
}

buildPath("/projects/:id/tasks/:taskId", { id: "1", taskId: "9" }); // ok
buildPath("/projects/:id/tasks/:taskId", { id: "1" }); // error: taskId missing

infer inside a template literal pattern extracts the parameter names from the route string itself. Forgetting a param is now a compile error, not a 404 in staging.

Key remapping for derived APIs

type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

type UserGetters = Getters<{ name: string; age: number }>;
// { getName: () => string; getAge: () => number }

Know when to stop

The heuristic I use: template literal types are worth it when they eliminate a runtime failure class (bad event name, missing route param). If the type exists only to impress reviewers, it will cost more in error-message archaeology than it saves. The best advanced types are the ones consumers never notice — autocomplete just works and invalid code just doesn't compile.

Keep Reading