TypeScript's template literal types are a feature that allows us to define string patterns with strict constraints. This is especially useful when working with structured text data, such as formatted identifiers, timestamps, or even currency values.

In this post, we'll explore template literal types through an example of enforcing a structured currency format in TypeScript.

Understanding template literal types

Template literal types allow us to define string formats using placeholders and unions. This is useful when we need to restrict possible values to specific patterns.

Consider the following TypeScript type definition for a currency string:

export type Currency = `${'-' | ''}${number}.${number}${number} ${'USD' | 'EUR' | 'GBP'}`;

Let's break this down step by step:

  1. ${'-' | ''} → The string can optionally start with a '-' (negative sign) or be empty.

    • Examples: "-", "" (no negative sign)
  2. ${number} → Represents any numeric value.

    • Example: "100", "9", "2500"
  3. . → A literal decimal point.

  4. ${number}${number} → Ensures exactly two decimal places.

    • Example: "00", "99", "45"
  5. ${'USD' | 'EUR' | 'GBP'} → A space followed by one of the allowed currency codes.

    • Example: "USD", "EUR", "GBP"

Valid and Invalid Examples

Let's see what values fit this type definition:

  • Valid Values:

    • "100.00 USD"
    • "9.99 EUR"
    • "-2500.50 GBP"
  • Invalid Values:

    • "100 USD" (Missing decimal places)
    • "9.9 EUR" (Only one decimal place)
    • "-50.999 GBP" (Too many decimal places)
    • "100.00 CAD" (Unsupported currency)
    • "USD 100.00" (Incorrect format order)

Why Use Template Literal Types?

Using template literal types in TypeScript provides several advantages:

  • Strict Format Enforcement: Ensures that all currency strings follow a consistent pattern.
  • Error Prevention: Reduces bugs caused by improperly formatted strings.
  • Type Safety: Improves the developer experience by catching mistakes at compile time.

Extending the Type

We can enhance this type to support more currencies or additional rules. For example, adding support for JPY (Japanese Yen), which doesn't use decimal places:

export type Currency =
  | `${'-' | ''}${number}.${number}${number} ${'USD' | 'EUR' | 'GBP'}`
  | `${'-' | ''}${number} JPY`;

Now, both "100.00 USD" and "5000 JPY" are valid!

Conclusion

TypeScript's template literal types allow us to define structured string formats with precision. By using them wisely, we can enhance type safety and enforce consistency in our applications.