Skip to main content

Template Literal Types

Template literal types construct string types from other types using backticks and ${...} interpolations. Unlike JavaScript template literals, they do not construct string values at runtime.

Basic Usage ≥0.339​

1type Field = 'name' | 'email';2type EventName = `${Field}Changed`;3
4'nameChanged' as EventName; // OK5'emailChanged' as EventName; // OK
Try

EventName is the union 'nameChanged' | 'emailChanged'.

When to use this​

Use template literal types for event names, string-based protocols, or strings with a required prefix or suffix. For unrelated constants, a plain union of literal types is simpler. Flow checks these annotations statically. They do not validate untrusted strings at runtime.

Literal Interpolation and Unions​

Each interpolated literal contributes its string representation. Flow formats numbers by their value, not their source spelling, and omits the n suffix from bigints. Booleans contribute 'true' or 'false'; null becomes 'null', and void becomes 'undefined'.

1type Label = `${'item'}-${0xff}`;2type State = `enabled:${true | false}`;3type Id = `id:${42n}`;4type Missing = `${null | void}`;5
6'item-255' as Label; // OK7'enabled:false' as State; // OK8'id:42' as Id; // OK9'undefined' as Missing; // OK10'item-0xff' as Label; // ERROR: the number's value is 255incompatible-typeCannot cast 'item-0xff' to Label because "item-0xff" [1] is incompatible with Label [2].
Try

When you put unions in multiple placeholders, Flow produces every combination of their members. This is called a cross-product:

1type Name = `${'a' | 'b'}-${'x' | 'y'}`;2
3'a-x' as Name; // OK4'b-y' as Name; // OK5'a-z' as Name; // ERRORincompatible-typeCannot cast 'a-z' to Name because "a-z" [1] is incompatible with Name [2].
Try

Name is exactly 'a-x' | 'a-y' | 'b-x' | 'b-y'.

Open-ended String Patterns​

The broad types string, number, and bigint describe sets of strings when used as placeholders. They do not make the template accept non-string values. A fixed prefix or suffix makes the template narrower than string, while ${string} alone accepts any string literal.

1type DataKey = `data-${string}`;2type NumericId = `item-${number}`;3type IntegerId = `item-${bigint}`;4
5'data-name' as DataKey; // OK6'item-1.5' as NumericId; // OK7'item-42' as IntegerId; // OK8'other-name' as DataKey; // ERROR: wrong prefixincompatible-typeCannot cast 'other-name' to DataKey because "other-name" [1] is incompatible with DataKey [2].9'item-many' as NumericId; // ERROR: not numeric textincompatible-typeCannot cast 'item-many' to NumericId because "item-many" [1] is incompatible with NumericId [2].10'item-1.5' as IntegerId; // ERROR: not integer textincompatible-typeCannot cast 'item-1.5' to IntegerId because "item-1.5" [1] is incompatible with IntegerId [2].11'item-42n' as IntegerId; // ERROR: the placeholder does not include nincompatible-typeCannot cast 'item-42n' to IntegerId because "item-42n" [1] is incompatible with IntegerId [2].12
13declare const key: DataKey;14key as string; // OK
Try

Numeric caveats​

${number} is not a strict decimal, CSS, or range validator. It accepts exponent and radix forms, surrounding whitespace, and even a whitespace-only numeric portion. It rejects NaN, Infinity, and numeric separators.

${bigint} follows different lexical rules. It rejects fractions, exponents, whitespace, a leading +, decimal leading zeros, and separators.

1type NumericText = `${number}`;2
3'1e3' as NumericText; // OK4'0xff' as NumericText; // OK5' ' as NumericText; // OK6'NaN' as NumericText; // ERRORincompatible-typeCannot cast 'NaN' to NumericText because "NaN" [1] is incompatible with NumericText [2].7'Infinity' as NumericText; // ERRORincompatible-typeCannot cast 'Infinity' to NumericText because "Infinity" [1] is incompatible with NumericText [2].8'1_000' as NumericText; // ERRORincompatible-typeCannot cast '1_000' to NumericText because "1_000" [1] is incompatible with NumericText [2].
Try

Generic Inference​

Flow can infer a type parameter from the part of a string matched by a placeholder:

1declare function stripPrefix<T extends string>(key: `data-${T}`): T;2
3stripPrefix('data-name') as 'name'; // OK4stripPrefix('data-name') as 'email'; // ERRORincompatible-typeCannot cast stripPrefix(...) to "email" because "name" [1] is incompatible with "email" [2].
Try

The declaration says that the function returns the suffix. You still need to implement that behavior at runtime.

Conditional Inference​

In a conditional type, infer extracts the part of a string that matches a placeholder. The false branch determines the result for strings that do not match:

1type DataName<S extends string> = S extends `data-${infer Name}` ? Name : empty;2
3'name' as DataName<'data-name'>; // OK4'email' as DataName<'data-name'>; // ERRORincompatible-typeCannot cast 'email' to DataName<"data-name"> because "email" [1] is incompatible with "name" [2].5'name' as DataName<'name'>; // ERROR: the result is emptyincompatible-typeCannot cast 'name' to DataName<"name"> because "name" [1] is incompatible with empty [2].
Try

String Casing​

You can combine templates with the existing Uppercase, Lowercase, Capitalize, and Uncapitalize utilities:

1type EventName = `on${Capitalize<'click' | 'change'>}`;2
3'onClick' as EventName; // OK4'onChange' as EventName; // OK5'onclick' as EventName; // ERRORincompatible-typeCannot cast 'onclick' to EventName because "onclick" [1] is incompatible with EventName [2].
Try

Templates as Mapped Keys​

Use a template literal type in a mapped type's as clause to rename properties, such as turning mark into onmark. The key remapping example keeps each original property's value type. See filtering keys when the source also has symbol keys.

Limits​

Placeholders must be compatible with string | number | bigint | boolean | null | void. Flow does not support object-like types or unknown as placeholders:

1type ObjectText = `${{name: string}}`; // ERRORinvalid-template-literal-typeTemplate literal type placeholder must be a subtype of string | number | bigint | boolean | null | undefined.2type UnknownText = `${unknown}`; // ERRORinvalid-template-literal-typeTemplate literal type placeholder must be a subtype of string | number | bigint | boolean | null | undefined.
Try

Flow will not expand a union cross-product with more than 10,000 combinations. Keep combinations small, or generate an explicit union ahead of time to avoid expanding a large set during type checking.

Adoption​

To use template literal types, set experimental.tslib_syntax=true in .flowconfig and use Flow and flow-parser 0.339.0+ and Prettier 3.10.0+ with its built-in flow parser.

See Also​