TypeScript Template Literal Types Cheat Sheet
String manipulation at the type level -- template literal types, built-in string utilities, key remapping, and real-world patterns.
Basic Template Literal Type
Template literal types use the same backtick syntax as JavaScript template literals, but at the type level.
type Greeting = `Hello, ${string}`;
const a: Greeting = 'Hello, Alice'; // OK
const b: Greeting = 'Hello, Bob'; // OK
// const c: Greeting = 'Hi, Alice'; // Error: doesn't match patterntype Vertical = 'top' | 'bottom';
type Horizontal = 'left' | 'right';
type Position = `${Vertical}-${Horizontal}`;
// => 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
const pos: Position = 'top-left'; // OK
// const bad: Position = 'center'; // Errortype Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
type HexPrefix = `0x${Digit}${Digit}`;
// => '0x00' | '0x01' | ... | '0x99'
const hex: HexPrefix = '0x1F'; // Error: not in the union
const ok: HexPrefix = '0x42'; // OKTemplate literal types distribute over unions. Each member of each union slot combines with every member of other slots, creating a cartesian product.
String Union Combinations
Combine two or more string literal unions to generate every possible combination automatically.
type Size = 'sm' | 'md' | 'lg';
type Side = 'top' | 'right' | 'bottom' | 'left';
type SpacingClass = `${Side}-${Size}`;
// => 'top-sm' | 'top-md' | 'top-lg' | 'right-sm' | ... (12 total)
const cls: SpacingClass = 'bottom-lg'; // OKtype DOMEvent = 'click' | 'focus' | 'blur' | 'change';
type Handler = `on${Capitalize<DOMEvent>}`;
// => 'onClick' | 'onFocus' | 'onBlur' | 'onChange'
const handler: Handler = 'onClick'; // OKtype Entity = 'user' | 'post' | 'comment';
type Getter = `get${Capitalize<Entity>}`; // 'getUser' | 'getPost' | 'getComment'
type CreatedEvent = `${Entity}Created`; // 'userCreated' | 'postCreated' | 'commentCreated'
type TableName = `${Entity}s`; // 'users' | 'posts' | 'comments'Built-in String Manipulation Types
TypeScript provides four intrinsic types that transform string literal types. They work on individual literals and distribute over unions.
type Upper = Uppercase<'hello'>; // => 'HELLO'
type Lower = Lowercase<'HELLO'>; // => 'hello'
type Shout = Uppercase<'yes' | 'no'>; // => 'YES' | 'NO'
// Practical: environment variable names
type EnvKey = 'apiUrl' | 'dbHost';
type EnvVar = `NEXT_PUBLIC_${Uppercase<EnvKey>}`;
// => 'NEXT_PUBLIC_APIURL' | 'NEXT_PUBLIC_DBHOST'type Cap = Capitalize<'hello'>; // => 'Hello'
type Uncap = Uncapitalize<'Hello'>; // => 'hello'
// Practical: getter/setter names from field names
type Field = 'name' | 'email' | 'age';
type Getter = `get${Capitalize<Field>}`;
// => 'getName' | 'getEmail' | 'getAge'
type Setter = `set${Capitalize<Field>}`;
// => 'setName' | 'setEmail' | 'setAge'// Screaming snake case from camelCase words
type Key = 'userId' | 'userName';
type ScreamSnake = Uppercase<Key>;
// => 'USERID' | 'USERNAME'
// These operate on individual characters only --
// they don't add underscores or split words.
// For full case conversion, you need recursive conditional types.These four types are compiler intrinsics -- they run at the type level during compilation. You can't define custom string manipulation types with the same performance.
Key Remapping with Template Literals
The `as` clause in mapped types lets you rename keys using template literal types. Added in TypeScript 4.1.
type Prefixed<T> = {
[K in keyof T as `data_${string & K}`]: T[K];
};
interface User {
name: string;
age: number;
}
type PrefixedUser = Prefixed<User>;
// => { data_name: string; data_age: number }type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface Config {
host: string;
port: number;
debug: boolean;
}
type ConfigGetters = Getters<Config>;
// => {
// getHost: () => string;
// getPort: () => number;
// getDebug: () => boolean;
// }// Remove keys whose values are functions
type DataOnly<T> = {
[K in keyof T as T[K] extends Function ? never : K]: T[K];
};
interface UserService {
name: string;
age: number;
save(): Promise<void>;
delete(): Promise<void>;
}
type UserData = DataOnly<UserService>;
// => { name: string; age: number }Remapping a key to `never` removes it from the output type. This is how you filter properties in mapped types.
Pattern Matching with Template Literals
Use `infer` inside template literal types to extract parts of string types. Think of it as regex for the type system.
type ExtractId<T> = T extends `user_${infer Id}` ? Id : never;
type A = ExtractId<'user_123'>; // => '123'
type B = ExtractId<'user_abc'>; // => 'abc'
type C = ExtractId<'post_456'>; // => never (no match)type Split<S extends string, D extends string> =
S extends `${infer Head}${D}${infer Tail}`
? [Head, ...Split<Tail, D>]
: [S];
type Parts = Split<'a.b.c', '.'>; // => ['a', 'b', 'c']
type Words = Split<'hello world', ' '>; // => ['hello', 'world']type ExtractParams<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? Param | ExtractParams<Rest>
: T extends `${string}:${infer Param}`
? Param
: never;
type Params = ExtractParams<'/users/:userId/posts/:postId'>;
// => 'userId' | 'postId'
// Use it to build a typed params object:
type RouteParams<T extends string> = {
[K in ExtractParams<T>]: string;
};
type UserPostParams = RouteParams<'/users/:userId/posts/:postId'>;
// => { userId: string; postId: string }Recursive template literal types are limited to about 1000 recursion levels. For deeply nested strings, TypeScript will bail out with a "type instantiation is excessively deep" error.
Real-World Patterns
Practical template literal type patterns from production TypeScript codebases.
type CSSUnit = 'px' | 'rem' | 'em' | '%' | 'vh' | 'vw';
type CSSValue = `${number}${CSSUnit}` | '0' | 'auto';
function setWidth(value: CSSValue): void {
// value is guaranteed to be a valid CSS length
}
setWidth('16px'); // OK
setWidth('1.5rem'); // OK
setWidth('100%'); // OK
setWidth('auto'); // OK
// setWidth('big'); // Errortype APIBase = '/api/v1';
type Resource = 'users' | 'posts' | 'comments';
type APIRoute = `${APIBase}/${Resource}`;
// => '/api/v1/users' | '/api/v1/posts' | '/api/v1/comments'
type WithId = `${APIRoute}/${string}`;
// => '/api/v1/users/...' | '/api/v1/posts/...' | ...
async function fetchResource(route: APIRoute): Promise<unknown> {
return fetch(route).then((r) => r.json());
}
fetchResource('/api/v1/users'); // OK
// fetchResource('/api/v2/users'); // Errortype EventConfig = {
user: { id: string; name: string };
post: { id: string; title: string };
};
type EventName = `${keyof EventConfig & string}:${'created' | 'updated' | 'deleted'}`;
// => 'user:created' | 'user:updated' | 'user:deleted'
// | 'post:created' | 'post:updated' | 'post:deleted'
type ParseEvent<T extends string> =
T extends `${infer Entity}:${infer Action}`
? { entity: Entity; action: Action }
: never;
type Info = ParseEvent<'user:created'>;
// => { entity: 'user'; action: 'created' }Limits and Gotchas
Template literal types are powerful but have sharp edges. Know these limits before going deep.
type Char = 'a' | 'b' | 'c' | 'd' | 'e'; // 5 members
type TwoChar = `${Char}${Char}`; // 25 combinations -- fine
type ThreeChar = `${Char}${Char}${Char}`; // 125 combinations -- still OK
// type FourChar = `${Char}${Char}${Char}${Char}`; // 625 combinations
// This compiles but slows down the type checker noticeably.
// At 5+ slots with large unions, you'll hit the 100,000 member limit.TypeScript caps union types at 100,000 members. Template literal types that generate unions beyond this limit produce an error: "Expression produces a union type that is too complex to represent."
type Valid = `id_${number}`; // OK
type AlsoValid = `is_${boolean}`; // OK: 'is_true' | 'is_false'
// type Invalid = `data_${object}`; // Error
// type AlsoInvalid = `items_${string[]}`; // Error
// Only these types can appear in template literal slots:
// string, number, bigint, boolean, and literal subtypes thereof// Recursive template literal types have a depth limit (~1000)
type Repeat<S extends string, N extends number, Acc extends string = ''> =
Acc extends { length: N }
? Acc
: Repeat<S, N, `${Acc}${S}`>;
// This pattern works for small N but hits the recursion
// limit for large values. Design types to avoid deep recursion.