Template Literal Types
Template literal types build onstring literal types, and have the ability to expand into many strings via unions.
They have the same syntax astemplate literal strings in JavaScript, but are used in type positions.When used with concrete literal types, a template literal produces a new string literal type by concatenating the contents.
tsTrytypeWorld ="world";typeGreeting =`hello${World }`;
When a union is used in the interpolated position, the type is the set of every possible string literal that could be represented by each union member:
tsTrytypeEmailLocaleIDs ="welcome_email" |"email_heading";typeFooterLocaleIDs ="footer_title" |"footer_sendoff";typeAllLocaleIDs =`${EmailLocaleIDs |FooterLocaleIDs }_id`;
For each interpolated position in the template literal, the unions are cross multiplied:
tsTrytypeAllLocaleIDs =`${EmailLocaleIDs |FooterLocaleIDs }_id`;typeLang ="en" |"ja" |"pt";typeLocaleMessageIDs =`${Lang }_${AllLocaleIDs }`;
We generally recommend that people use ahead-of-time generation for large string unions, but this is useful in smaller cases.
String Unions in Types
The power in template literals comes when defining a new string based on information inside a type.
Consider the case where a function (makeWatchedObject) adds a new functioncalledon() to a passed object. In JavaScript, its call might look like:makeWatchedObject(baseObject). We can imagine the base object as lookinglike:
tsTryconstpassedObject = {firstName :"Saoirse",lastName :"Ronan",age :26,};
Theon function that will be added to the base object expects two arguments, aneventName (astring) and acallback (afunction).
TheeventName should be of the formattributeInThePassedObject + "Changed"; thus,firstNameChanged as derived from the attributefirstName in the base object.
Thecallback function, when called:
- Should be passed a value of the type associated with the name
attributeInThePassedObject; thus, sincefirstNameis typed asstring, the callback for thefirstNameChangedevent expects astringto be passed to it at call time. Similarly events associated withageshould expect to be called with anumberargument - Should have
voidreturn type (for simplicity of demonstration)
The naive function signature ofon() might thus be:on(eventName: string, callback: (newValue: any) => void). However, in the preceding description, we identified important type constraints that we’d like to document in our code. Template Literal types let us bring these constraints into our code.
tsTryconstperson =makeWatchedObject ({firstName :"Saoirse",lastName :"Ronan",age :26,});// makeWatchedObject has added `on` to the anonymous Objectperson .on ("firstNameChanged", (newValue )=> {console .log (`firstName was changed to${newValue }!`);});
Notice thaton listens on the event"firstNameChanged", not just"firstName". Our naive specification ofon() could be made more robust if we were to ensure that the set of eligible event names was constrained by the union of attribute names in the watched object with “Changed” added at the end. While we are comfortable with doing such a calculation in JavaScript i.e.Object.keys(passedObject).map(x => `${x}Changed`), template literalsinside the type system provide a similar approach to string manipulation:
tsTrytypePropEventSource <Type > = {on (eventName :`${string&keyofType }Changed`,callback : (newValue :any)=>void):void;};/// Create a "watched object" with an `on` method/// so that you can watch for changes to properties.declarefunctionmakeWatchedObject <Type >(obj :Type ):Type &PropEventSource <Type >;
With this, we can build something that errors when given the wrong property:
tsTryconstperson =makeWatchedObject ({firstName :"Saoirse",lastName :"Ronan",age :26});person .on ("firstNameChanged", ()=> {});// Prevent easy human error (using the key instead of the event name)Argument of type '"firstName"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.2345Argument of type '"firstName"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.person .on ("firstName" , ()=> {});// It's typo-resistantArgument of type '"frstNameChanged"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.2345Argument of type '"frstNameChanged"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.person .on ("frstNameChanged" , ()=> {});
Inference with Template Literals
Notice that we did not benefit from all the information provided in the original passed object. Given change of afirstName (i.e. afirstNameChanged event), we should expect that the callback will receive an argument of typestring. Similarly, the callback for a change toage should receive anumber argument. We’re naively usingany to type thecallback’s argument. Again, template literal types make it possible to ensure an attribute’s data type will be the same type as that attribute’s callback’s first argument.
The key insight that makes this possible is this: we can use a function with a generic such that:
- The literal used in the first argument is captured as a literal type
- That literal type can be validated as being in the union of valid attributes in the generic
- The type of the validated attribute can be looked up in the generic’s structure using Indexed Access
- This typing information canthen be applied to ensure the argument to thecallback function is of the same type
tsTrytypePropEventSource <Type > = {on <Key extendsstring &keyofType >(eventName :`${Key }Changed`,callback : (newValue :Type [Key ])=>void):void;};declarefunctionmakeWatchedObject <Type >(obj :Type ):Type &PropEventSource <Type >;constperson =makeWatchedObject ({firstName :"Saoirse",lastName :"Ronan",age :26});person .on ("firstNameChanged",newName => {console .log (`new name is${newName .toUpperCase ()}`);});person .on ("ageChanged",newAge => {if (newAge < 0) {console .warn ("warning! negative age");}})
Here we madeon into a generic method.
When a user calls with the string"firstNameChanged", TypeScript will try to infer the right type forKey.To do that, it will matchKey against the content before"Changed" and infer the string"firstName".Once TypeScript figures that out, theon method can fetch the type offirstName on the original object, which isstring in this case.Similarly, when called with"ageChanged", TypeScript finds the type for the propertyage which isnumber.
Inference can be combined in different ways, often to deconstruct strings, and reconstruct them in different ways.
Intrinsic String Manipulation Types
To help with string manipulation, TypeScript includes a set of types which can be used in string manipulation. These types come built-in to the compiler for performance and can’t be found in the.d.ts files included with TypeScript.
Uppercase<StringType>
Converts each character in the string to the uppercase version.
Example
tsTrytypeGreeting ="Hello, world"typeShoutyGreeting =Uppercase <Greeting >typeASCIICacheKey <Str extendsstring> =`ID-${Uppercase <Str >}`typeMainID =ASCIICacheKey <"my_app">
Lowercase<StringType>
Converts each character in the string to the lowercase equivalent.
Example
tsTrytypeGreeting ="Hello, world"typeQuietGreeting =Lowercase <Greeting >typeASCIICacheKey <Str extendsstring> =`id-${Lowercase <Str >}`typeMainID =ASCIICacheKey <"MY_APP">
Capitalize<StringType>
Converts the first character in the string to an uppercase equivalent.
Example
tsTrytypeLowercaseGreeting ="hello, world";typeGreeting =Capitalize <LowercaseGreeting >;
Uncapitalize<StringType>
Converts the first character in the string to a lowercase equivalent.
Example
tsTrytypeUppercaseGreeting ="HELLO WORLD";typeUncomfortableGreeting =Uncapitalize <UppercaseGreeting >;
Technical details on the intrinsic string manipulation types
The code, as of TypeScript 4.1, for these intrinsic functions uses the JavaScript string runtime functions directly for manipulation and are not locale aware.
function applyStringMapping(symbol: Symbol, str: string) { switch (intrinsicTypeKinds.get(symbol.escapedName as string)) { case IntrinsicTypeKind.Uppercase: return str.toUpperCase(); case IntrinsicTypeKind.Lowercase: return str.toLowerCase(); case IntrinsicTypeKind.Capitalize: return str.charAt(0).toUpperCase() + str.slice(1); case IntrinsicTypeKind.Uncapitalize: return str.charAt(0).toLowerCase() + str.slice(1); } return str;}The TypeScript docs are an open source project. Help us improve these pagesby sending a Pull Request ❤
Last updated: Nov 25, 2025