Skip to content
JL

Home

About

Blog

Contact

Shop

Portfolio

Privacy

TOS

Click to navigate

  1. Home
  2. Joshua R. Lehman's Blog
  3. Template Literal Types in TypeScript

Table of contents

  • Share on X
  • Discuss on X

Related Articles

Indexed Access Types in TypeScript
TypeScript
7m
Jul 26, 2026

Indexed Access Types in TypeScript

Indexed access types use the T[K] syntax to look up property types — the same bracket notation you use at runtime, but at the type level. They let you extract any type that lives inside another type without duplicating it.

#Indexed Access Types#TypeScript Type Lookup+5
Mapped Types: Transforming Types in TypeScript
TypeScript
10m
Jul 12, 2026

Mapped Types: Transforming Types in TypeScript

Mapped types are TypeScript's for-loop for types. They iterate over the keys of an existing type and produce a new type with transformed properties — the same way Array.map transforms every element in an array.

#Mapped Types#TypeScript Transformation+5
keyof and typeof Operators in TypeScript
TypeScript
8m
Jul 19, 2026

keyof and typeof Operators in TypeScript

keyof extracts the union of property names from a type. typeof extracts the type of a value at compile time. Together they bridge the gap between your runtime values and your type-level code — letting you derive types from what already exists.

#keyof Operator#typeof Operator+5
Ask me anything! 💬

© Joshua R. Lehman

Full Stack Developer

Crafted with passion • Built with modern web technologies

2026 • All rights reserved

Contents

  • What Are Template Literal Types
  • Basic Template Literal Type Syntax
  • Combining with Union Types
  • Intrinsic String Manipulation Types
  • Pattern Matching at Type Level
  • Real-World Patterns
  • Best Practices
  • What Is Next
  • Key Takeaways
TypeScript

Template Literal Types in TypeScript

August 2, 2026•7 min read
Joshua R. Lehman
Joshua R. Lehman
Author
TypeScript template literal types for string manipulation at the type level
Template Literal Types in TypeScript

Template literal types arrived in TypeScript 4.1 and gave string manipulation a permanent home in the type system. They look exactly like JavaScript's template literals — backticks, ${} interpolation — but they construct types, not values. Combined with mapped types and conditional types, they let you encode string patterns, auto-generate related string types, and validate that function arguments follow naming conventions — all at compile time. In this post, you'll learn the syntax, how distribution over unions works, the built-in string manipulation utility types, and several real-world patterns.

TypeScript 4.1+

Template literal types were introduced in TypeScript 4.1. They're fully supported in all modern TypeScript versions. If you're on an older version and see unexpected errors with backtick types, upgrading TypeScript will resolve them.

What Are Template Literal Types

A template literal type uses backtick syntax and ${} interpolation to construct new string types from existing ones:

type Greeting = `Hello, ${string}`;
 
// Greeting matches any string starting with "Hello, "
const g1: Greeting = "Hello, World"; // OK
const g2: Greeting = "Hello, Alice"; // OK
// const g3: Greeting = "Hi, Alice";  // Error: doesn't match the pattern

When you interpolate a specific string literal or union type, TypeScript resolves the combinations:

type Direction = "North" | "South";
type Speed = "Fast" | "Slow";
 
type Movement = `${Speed}${Direction}`;
// "FastNorth" | "FastSouth" | "SlowNorth" | "SlowSouth"

Basic Template Literal Type Syntax

The simplest form combines two string literals:

type EventName = `on${string}`;
// Matches "onClick", "onChange", "onSubmit", etc.
 
type CSSUnit = `${number}px` | `${number}em` | `${number}rem`;
// "10px", "1.5em", "2rem" all match — but TypeScript can't fully verify runtime values
 
// Combining concrete string literals
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type Endpoint = "/users" | "/posts" | "/comments";
type Route = `${HttpMethod} ${Endpoint}`;
// "GET /users" | "GET /posts" | ... (12 combinations total)

You can interpolate any type that has a string representation — string, number, boolean, bigint, and string/number/boolean literals:

type Px<T extends number> = `${T}px`;
 
type TenPx = Px<10>; // "10px"
type HalfPx = Px<0.5>; // "0.5px"

Combining with Union Types

When you interpolate a union type, the template literal type distributes over the union, producing a union of all combinations:

type Axis = "x" | "y" | "z";
type Transform = "translate" | "rotate" | "scale";
 
type CSSTransform = `${Transform}${Capitalize<Axis>}`;
// "translateX" | "translateY" | "translateZ"
// | "rotateX" | "rotateY" | "rotateZ"
// | "scaleX" | "scaleY" | "scaleZ"

This distribution happens for each interpolated position independently — three transforms × three axes = nine combinations, all inferred automatically.

Combinatorial Explosion

Distribution is powerful but can produce very large union types. Three positions each with 10 options = 1000 combinations. TypeScript will still type-check them all, but IDE autocompletion and type error messages become unwieldy. Keep unions small when using multiple interpolation positions.

Intrinsic String Manipulation Types

TypeScript ships four built-in utility types for string casing, backed by compiler intrinsics:

type U = Uppercase<"hello">; // "HELLO"
type L = Lowercase<"HELLO">; // "hello"
type C = Capitalize<"hello">; // "Hello"
type UC = Uncapitalize<"Hello">; // "hello"

These work on union types too:

type Colors = "red" | "green" | "blue";
type UpperColors = Uppercase<Colors>; // "RED" | "GREEN" | "BLUE"
type CapColors = Capitalize<Colors>; // "Red" | "Green" | "Blue"

Combined with template literals and mapped types, they power patterns like auto-generating getter and setter names:

type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
 
type Setters<T> = {
  [K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
};
 
interface UserState {
  name: string;
  age: number;
  active: boolean;
}
 
type UserGetters = Getters<UserState>;
// { getName: () => string; getAge: () => number; getActive: () => boolean }
 
type UserSetters = Setters<UserState>;
// { setName: (value: string) => void; setAge: (value: number) => void; ... }

Pattern Matching at Type Level

Template literal types can be used with infer to extract parts of a string type:

// Extract the event name from an "on{Event}" string
type ExtractEventName<T extends string> = T extends `on${infer EventName}`
  ? EventName
  : never;
 
type Click = ExtractEventName<"onClick">; // "Click"
type Change = ExtractEventName<"onChange">; // "Change"
type Raw = ExtractEventName<"submit">; // never — doesn't match
 
// Extract prefix and suffix from a formatted string
type ParseRoute<T extends string> = T extends `${infer Method} ${infer Path}`
  ? { method: Method; path: Path }
  : never;
 
type Parsed = ParseRoute<"GET /users">;
// { method: "GET"; path: "/users" }

String Parsing at the Type Level

Combining template literals with infer lets you parse string patterns at the type level — extracting meaningful pieces from formatted strings like routes, event names, or CSS property strings. This was impossible before TypeScript 4.1.

Real-World Patterns

Here's a complete example — a type-safe event emitter where event handler names are derived from event names:

type EventMap = {
  click: { x: number; y: number };
  keydown: { key: string; code: string };
  resize: { width: number; height: number };
};
 
type EventHandlers = {
  [K in keyof EventMap as `on${Capitalize<string & K>}`]: (
    event: EventMap[K]
  ) => void;
};
 
// EventHandlers is:
// {
//   onClick: (event: { x: number; y: number }) => void;
//   onKeydown: (event: { key: string; code: string }) => void;
//   onResize: (event: { width: number; height: number }) => void;
// }
 
class TypedEmitter {
  private handlers: Partial<EventHandlers> = {};
 
  on<K extends keyof EventHandlers>(event: K, handler: EventHandlers[K]): void {
    this.handlers[event] = handler;
  }
}
 
const emitter = new TypedEmitter();
emitter.on("onClick", ({ x, y }) => console.log(x, y)); // fine
// emitter.on("onMissing", () => {}); // Error: not a valid event

Another pattern: type-safe CSS-in-JS property names:

type CSSProperty = "margin" | "padding" | "border" | "background";
 
type CSSDirection = "Top" | "Right" | "Bottom" | "Left";
 
type DirectionalCSS = `${CSSProperty}${CSSDirection}`;
// "marginTop" | "marginRight" | "paddingTop" | "borderTop" | ... (16 combinations)
 
function setStyle(property: DirectionalCSS, value: string): void {
  document.body.style[property as any] = value;
}
 
setStyle("marginTop", "16px"); // fine
// setStyle("marginMiddle", "0"); // Error: not a valid CSS property

Best Practices

Use template literals to encode string conventions as types. If your codebase has naming conventions — event handlers must start with on, API routes must start with /, action types follow ENTITY/ACTION — encode them as template literal types.

Keep union sizes manageable. Two positions with 5 options each = 25 combinations, which is fine. Three positions with 10 options = 1000 combinations, which causes slow type checking and unreadable error messages.

Prefer template literals over regex-style string validation. Template literal types are far more composable and give better error messages than string with a comment saying "must follow this format."

Extract with infer for parsing patterns. Whenever you need to pull a segment out of a typed string, combine template literals with infer in a conditional type.

Anti-Pattern

Don't use template literal types for arbitrary string validation that changes at runtime. They're a compile-time tool — they check literal types, not runtime values. For runtime string validation, use a validation library like Zod.

What Is Next

You've now completed the core building blocks of TypeScript's advanced type system: conditional types, infer, mapped types, keyof/typeof, indexed access, and template literal types. The next series of posts dives into utility types in depth — deconstructing Partial, Required, Readonly, Record, Pick, Omit, Exclude, Extract, and how to compose them into custom utilities.

Key Takeaways

  • Template literal types use backtick syntax to construct new string types from existing ones
  • When you interpolate a union type, the template literal distributes over each member and produces a union of all combinations
  • TypeScript ships four intrinsic string manipulation types: Uppercase, Lowercase, Capitalize, Uncapitalize
  • Combining template literals with infer enables string pattern parsing at the type level
  • Template literal types are compile-time tools — they validate literal types, not runtime string values

Phase 5 Advanced Generics Complete

You've now mastered the complete advanced generics toolkit: conditional types, infer, mapped types, keyof, typeof, indexed access, and template literals. The patterns these tools enable — type-safe events, auto-generated getters, route typing — are what make production TypeScript codebases a joy to maintain.