Skip to content

Template Literal Types

Template literal types look like JavaScript template strings, but they operate at the type level:

type Greeting = `Hello, ${string}`

This means:

“Any string that starts with Hello, followed by anything.”

Example:

let g: Greeting

g = "Hello, world"     // ✔ OK
g = "Hello, TypeScript" // ✔ OK
g = "Hi there"          // ❌ Error

Template literal types let you build pattern‑based string types.


Building String‑Based Types

Let’s start with simple combinations.


Combining unions

type Size = "small" | "medium" | "large"
type Color = "red" | "blue"

type Variant = `${Size}-${Color}`

Result:

// "small-red" | "small-blue" |
// "medium-red" | "medium-blue" |
// "large-red" | "large-blue"

This is incredibly useful for:

  • CSS utility classes
  • design systems
  • component variants

Prefixing and suffixing

type EventName<T extends string> = `on${Capitalize<T>}`

Usage:

type E = EventName<"click" | "hover">
// "onClick" | "onHover"

This is how many UI frameworks type event handlers.


Interpolating numbers

type Page = `page-${number}`

Valid:

let p: Page = "page-1"

Invalid:

p = "page-one" // ❌

Enforcing Naming Conventions

Template literal types can enforce naming rules at compile time.


Enforce camelCase

type CamelCaseKey = `${Lowercase<string>}${string}`

Or more strictly:

type CamelCase<T extends string> =
  T extends `${infer First}_${infer Rest}`
    ? `${First}${Capitalize<CamelCase<Rest>>}`
    : T

Usage:

type C = CamelCase<"user_name"> // "userName"

This is how ORMs and API clients generate typed field names.


Enforce snake_case

type SnakeCase<T extends string> =
  T extends `${infer First}${infer Rest}`
    ? First extends Lowercase<First>
      ? `${First}${SnakeCase<Rest>}`
      : `_${Lowercase<First>}${SnakeCase<Rest>}`
    : T

Usage:

type S = SnakeCase<"userName"> // "user_name"

Restrict allowed formats

type KebabCase = `${Lowercase<string>}-${Lowercase<string>}`

Valid:

let k: KebabCase = "user-name"

Invalid:

k = "User-Name" // ❌ uppercase not allowed

Real‑World Example: Typed API Routes

type Resource = "users" | "posts" | "comments"
type Route = `/${Resource}/${number}`

Usage:

let r: Route

r = "/users/1"     // ✔
r = "/posts/42"    // ✔
r = "/likes/10"    // ❌ "likes" not allowed

This is how frameworks like tRPC and Remix type their route systems.


Real‑World Example: Typed Event Emitters

type Events = {
  "user:created": { id: string }
  "user:deleted": { id: string }
}

type EventName = keyof Events

You can enforce event naming conventions:

type EventPrefix = `user:${"created" | "deleted"}`

This prevents typos like "user:create".


Real‑World Example: Typed CSS Utility Classes

type Spacing = 0 | 1 | 2 | 3 | 4
type Direction = "top" | "bottom" | "left" | "right"

type MarginClass = `m-${Direction}-${Spacing}`

Usage:

let m: MarginClass

m = "m-top-2"     // ✔
m = "m-left-10"   // ❌ 10 not allowed
m = "margin-top"  // ❌ wrong format

This is how Tailwind‑style systems can be typed.


Combining Template Literals with Conditional Types

Template literal types become extremely powerful when combined with conditional types.

Example: Extract prefix

type Prefix<T> =
  T extends `${infer P}-${string}` ? P : never

type A = Prefix<"user-created"> // "user"

Example: Extract suffix

type Suffix<T> =
  T extends `${string}-${infer S}` ? S : never

type B = Suffix<"user-created"> // "created"

This is how you build type‑safe event parsing, routing, and logging systems.


Putting It All Together

Here’s a realistic example:

Generate getter method names from object keys.

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

Usage:

type User = { id: string; name: string }

type UserGetters = Getters<User>
// {
//   getId: () => string
//   getName: () => string
// }

This combines:

  • template literal types
  • key remapping
  • capitalization helpers

This is the foundation of many advanced libraries.


Summary

In this lesson, you learned how template literal types let you build expressive, pattern‑based string types:

1. Building string‑based types

  • combine unions
  • prefix/suffix keys
  • interpolate numbers
  • generate dynamic string unions

2. Enforcing naming conventions

  • camelCase
  • snake_case
  • kebab-case
  • event naming patterns
  • API route formats

Template literal types unlock a new dimension of TypeScript’s type system—letting you model string patterns with the same precision you model objects and functions.