Template Literal Types: Dynamic String Typing

Table of Contents

  • Introduction
  • What Are Template Literal Types?
  • Basic Syntax of Template Literal Types
  • Combining Static and Dynamic Parts in Template Literals
  • Advanced Usage of Template Literals
    • Creating String Patterns
    • Conditional Types with Template Literals
  • Working with String Literals and Unions
  • Using Template Literal Types with Enums and Other Types
  • Practical Examples
    • Example 1: Enforcing String Patterns
    • Example 2: Combining Union Types and Template Literals
  • Best Practices for Using Template Literal Types
  • Conclusion

Introduction

Template literal types in TypeScript are a powerful feature that allows you to construct string types dynamically by combining literal strings with expressions. They offer the flexibility to define types that match specific patterns of strings, similar to how template literals work in JavaScript, but with the added power of type safety.

This feature can be used to enforce rules on strings, generate types based on patterns, and even combine multiple types into one. By the end of this article, you’ll understand how to leverage template literal types to create more precise and dynamic string typings in TypeScript.


What Are Template Literal Types?

Template literal types enable the creation of string types based on the structure of string literals. They allow you to express types that resemble template literals in JavaScript, but with the added ability to include type expressions.

A template literal type is defined using the `backtick (“) syntax and can combine literal types with placeholders for type expressions, like so:

type Greeting = `Hello, ${string}!`;

In this case, Greeting is a type that can represent any string starting with “Hello, ” and followed by any string, finishing with an exclamation mark.


Basic Syntax of Template Literal Types

The basic syntax for a template literal type is as follows:

type SomeType = `prefix-${string}-suffix`;

Here, SomeType can represent any string that starts with “prefix-“, followed by any string (string), and ends with “-suffix”.

In more detail:

  • ${} allows you to embed expressions or types inside the string template.
  • string is a type that represents any string, but you can use more specific types as needed.

Example:

type FileName = `file-${number}.txt`;

Here, FileName represents any string that matches the pattern of "file-", followed by a number, and ending with ".txt".


Combining Static and Dynamic Parts in Template Literals

Template literal types can combine static string parts (literal text) with dynamic parts (type placeholders). This allows you to enforce specific patterns while still allowing flexibility in the type.

Example:

type DateString = `2022-${'01' | '02' | '03'}-${string}`;

In this example, DateString is a type that expects a string beginning with 2022-, followed by one of the strings “01”, “02”, or “03”, and then a hyphen and any other string. This is useful when you want to represent dates with specific months but without rigidly defining the full date structure.


Advanced Usage of Template Literals

Template literal types can be combined with other TypeScript features, such as conditional types and union types, to create more complex and flexible string patterns.

Creating String Patterns

One common use case of template literal types is to enforce a specific pattern in strings. You can combine literal types, unions, and other types to create a string pattern that matches certain conditions.

type UserRole = `admin-${string}` | `user-${string}`;

In this example, UserRole can be any string that starts with admin- or user- and is followed by any string. This is useful for user roles that require a dynamic identifier but follow a predefined structure.

Conditional Types with Template Literals

Template literal types can also be used with conditional types to create complex patterns based on conditions.

type Status = 'active' | 'inactive';
type StatusMessage = Status extends 'active' ? `User is ${Status}` : `User is ${Status}`;

Here, the StatusMessage type will either be User is active or User is inactive, depending on the value of the Status.


Working with String Literals and Unions

You can combine template literal types with union types to generate even more flexible string patterns. A union of template literal types allows you to define a range of valid string patterns.

Example:

type ButtonType = `primary-${'submit' | 'reset'}` | `secondary-${'submit' | 'reset'}`;

In this case:

  • ButtonType will allow any string that starts with primary- or secondary-, followed by either submit or reset.
  • This can represent different types of buttons in a UI, ensuring that the string matches the pattern for button types.

Using Template Literal Types with Enums and Other Types

Template literal types can also be combined with other types like enums and type aliases to create more complex and reusable patterns.

Example: Template Literals with Enums

enum FileType {
JPEG = 'jpeg',
PNG = 'png',
GIF = 'gif'
}

type FileName = `image-${FileType}.${string}`;

Here:

  • FileName is a type that expects strings starting with image-, followed by one of the values from the FileType enum, and ending with any string (e.g., "image-jpeg.abc").

By combining enums with template literals, you can create dynamic, but well-defined string patterns that represent structured data.


Practical Examples

Example 1: Enforcing String Patterns

type EmailPattern = `${string}@${string}.${'com' | 'org' | 'net'}`;

In this example, EmailPattern enforces a pattern where the string must contain an “@” symbol, followed by a domain name, and then ending with a top-level domain of either “com”, “org”, or “net”.

Example 2: Combining Union Types and Template Literals

type UserId = `${'user' | 'admin'}-${number}`;

This type represents a string that starts with either “user-” or “admin-” and is followed by a number. It’s a common pattern for user identification, ensuring both flexibility and consistency in the string format.

Example 3: Combining Multiple Patterns

type PhoneNumber = `+${number}-${number}-${number}`;

This template literal type enforces a phone number format like +123-456-7890, where each part is a number, but the exact digits are flexible.


Best Practices for Using Template Literal Types

  1. Use Template Literals for Enforcing Patterns: Template literals are most useful when you need to enforce a specific string pattern, such as file names, user IDs, or status codes.
  2. Combine with Union Types for Flexibility: Combine template literals with union types to create more complex patterns and increase the flexibility of your types.
  3. Use with Enums for Strong Typing: When working with predefined sets of values, combining template literal types with enums can provide a type-safe way to generate structured strings.
  4. Be Aware of Performance: While template literals provide great flexibility, using them excessively in very large codebases might impact performance, so use them judiciously in scenarios that require dynamic string typing.

Conclusion

Template literal types are a powerful feature in TypeScript that allow you to define string types dynamically based on patterns. They give you the ability to enforce specific string formats, combine multiple types, and even use conditional logic to determine string content. By combining template literals with union types, enums, and other TypeScript features, you can create flexible and type-safe patterns for a variety of scenarios.

With the knowledge from this article, you can start applying template literal types to your own projects, ensuring better type safety and consistency in your code. Whether you’re enforcing URL formats, file names, or dynamic identifiers, template literal types offer a clean and efficient solution.