TypeScript Template Literal Types
Template Literal Types build on string literal types and have the ability to expand into many strings via unions. They have the same syntax as template literal strings in JavaScript, but are used in type positions.
1. Basic Syntax
When used with concrete string types, a template literal produces a new string literal type by concatenating the contents.
type World = "world";
type Greeting = `hello ${World}`;
// type Greeting = "hello world"
2. Union Types
The real power comes when you use unions inside the interpolation. TypeScript distributes the union over the template literal, creating a cross-product of all possibilities.
type Color = "red" | "blue";
type Quantity = "one" | "two";
type Item = `${Color}-${Quantity}`;
// type Item = "red-one" | "red-two" | "blue-one" | "blue-two"
3. Intrinsic String Manipulation Types
TypeScript provides special types to help with string manipulation within types.
Uppercase<StringType>Lowercase<StringType>Capitalize<StringType>Uncapitalize<StringType>
type Greeting = "Hello, world";
type ShoutyGreeting = Uppercase<Greeting>;
// type ShoutyGreeting = "HELLO, WORLD"
type ASCIICacheKey<Str extends string> = `ID-${Uppercase<Str>}`;
type MainID = ASCIICacheKey<"my_app">;
// type MainID = "ID-MY_APP"
4. Practical Example: Event Listeners
You can use template literals to type dynamic event names.
type PropEventSource<Type> = {
on(eventName: `${string & keyof Type}Changed`, callback: (newValue: any) => void): void;
};
declare function makeWatchedObject<Type>(obj: Type): Type & PropEventSource<Type>;
const person = makeWatchedObject({
firstName: "Saoirse",
lastName: "Ronan",
age: 26
});
// TypeScript expects "firstNameChanged" | "lastNameChanged" | "ageChanged"
person.on("firstNameChanged", (newValue) => {
console.log(`firstName was changed to ${newValue}!`);
});
// person.on("firstName", () => {}); // Error