Template Literal Types

Advanced · 20 min read · ▶ live playground · ✦ checkpoint

Template literal types let TypeScript describe strings by pattern. A type like `user:${string}` means "a string that starts with user: and then has more string content."

Use them when your app already treats strings as structured names: cache keys, event names, command names, route paths, and derived method names.

Strings With Holes

A template literal type looks like a JavaScript template string, but it lives in the type layer. The holes contain types, not runtime expressions.

typescript — playgroundtype-checked
⌘/Ctrl + Enter to run

UserCacheKey is wider than one exact literal like "user:ada", but narrower than plain string. It accepts "user:ada", "user:42", and "user:"; it rejects strings that do not match the pattern.

OrderCacheKey uses a number hole, so strings like "order:42" fit. Template literal types still check string shapes, not runtime values. They do not parse numbers at runtime or change the emitted JavaScript.

Unions Multiply

When a template literal type contains a union, TypeScript expands every combination. With two holes, the combinations multiply.

typescript — playgroundtype-checked
⌘/Ctrl + Enter to run

EventName is not a fuzzy string. It is exactly:

type EventName =
  | "user:created"
  | "user:deleted"
  | "team:created"
  | "team:deleted";

The playground's break-it line verifies the same fact from the other direction: "user:archived" is rejected because it is not one of those four generated strings.

Built-In Casing Helpers

TypeScript ships four string-manipulation helpers for template literal types:

  • Uppercase<T> makes letters uppercase.
  • Lowercase<T> makes letters lowercase.
  • Capitalize<T> uppercases the first letter.
  • Uncapitalize<T> lowercases the first letter.

They work on string literal types and unions of string literal types.

typescript — playgroundtype-checked
⌘/Ctrl + Enter to run

These helpers are type-level string transforms. They do not call .toUpperCase() at runtime. They help you derive names like onSave, GET, or readyFlag without writing separate unions by hand.

Parse A Route Segment

Template literal types can also match against string patterns. Add infer, and the checker can name the piece it found.

This parser is deliberately shallow: it extracts the first :param segment from a route. Recursive route parsing belongs to the next lesson.

typescript — playgroundtype-checked
⌘/Ctrl + Enter to run

Read FirstParam out loud with the conditional-type voice from Section 10: if the path fits "some text, then :, then a parameter name, then /, then more text," return the parameter name. Otherwise, if it fits "some text, then :, then a parameter name," return that parameter name. Otherwise, return never.

That never flows into the mapped type in RouteParams. If there is no parameter, no key is produced.

URL Autocomplete

Hono and tRPC-style APIs use this family of ideas to make string routes feel less stringly typed. A framework can carry a union of known paths through generic types, then make a client or router method accept only those paths. The editor can complete the route string because the parameter type is a generated string union, not plain string.

Here is the small version:

typescript — playgroundtype-checked
⌘/Ctrl + Enter to run

The route function still receives ordinary strings at runtime. The type layer adds the editor experience: known path autocomplete, typo rejection, and parameter object shapes derived from the chosen path.

Template literal types can generate and parse strings. Next lesson asks how far that should go: recursive types can parse deeper structures, but they come with readability and performance limits.

Checkpoint

Answer all three to mark this lesson complete

+50 XP on completion