This TypeScript error occurs when you attempt to use an invalid expression or operation within a template literal type definition. Template literal types only allow specific type-level constructs like type parameters, unions, and intrinsic string manipulation types, not runtime expressions or complex type operations.
The "Cannot use expression in template literal type" error appears when TypeScript encounters an expression that isn't valid within the template literal type syntax. Template literal types, introduced in TypeScript 4.1, allow you to create new string types by combining and transforming existing string literal types at the type level. However, template literal types have strict limitations on what can be placed inside the ${} interpolation syntax. You can only use: - Type parameters and generic type variables - Union types of string literals - Intrinsic string manipulation types (`Uppercase<T>`, `Lowercase<T>`, `Capitalize<T>`, `Uncapitalize<T>`) - Other template literal types - String literal types You cannot use: - Conditional types directly in the template - Array indexing or property access on types - Mapped types - Complex type operations - Runtime values or expressions This restriction exists because template literal types operate purely at the type level during compilation, and certain type operations cannot be safely or efficiently computed within the template literal syntax.
Move conditional types and complex operations outside the template literal:
// WRONG - conditional type directly in template
type Message<T> = \`Value is \${T extends string ? T : never}\`;
// Error: Cannot use expression in template literal type
// CORRECT - extract conditional to separate type
type ExtractString<T> = T extends string ? T : never;
type Message<T> = \`Value is \${ExtractString<T>}\`;
// Example usage:
type StringMsg = Message<"hello">; // "Value is hello"
type NumberMsg = Message<42>; // "Value is never"This approach separates the conditional logic from the template literal, which is a supported pattern.
Ensure you're only using supported expressions inside template literals:
// VALID - type parameters
type Greeting<Name extends string> = \`Hello, \${Name}\`;
type MyGreeting = Greeting<"World">; // "Hello, World"
// VALID - union types
type Size = "small" | "medium" | "large";
type SizeClass = \`size-\${Size}\`; // "size-small" | "size-medium" | "size-large"
// VALID - intrinsic string types
type ShoutGreeting<T extends string> = \`HELLO, \${Uppercase<T>}\`;
type Loud = ShoutGreeting<"world">; // "HELLO, WORLD"
// VALID - combining multiple unions
type Color = "red" | "blue";
type Shade = "light" | "dark";
type ColorShade = \`\${Shade}-\${Color}\`;
// "light-red" | "light-blue" | "dark-red" | "dark-blue"
// INVALID - property access
type User = { name: string };
type UserName<T> = \`User: \${T["name"]}\`; // Error!
// INVALID - conditional in template
type Maybe<T> = \`\${T extends string ? T : "unknown"}\`; // Error!Stick to type parameters, unions, and the four intrinsic string manipulation utilities.
If you need to access object properties in a template literal, extract them first:
// WRONG - accessing property inside template
type Event = { type: "click" | "hover" };
type EventHandler = \`on\${Capitalize<Event["type"]>}\`; // Error!
// CORRECT - extract property first
type Event = { type: "click" | "hover" };
type EventType = Event["type"]; // "click" | "hover"
type EventHandler = \`on\${Capitalize<EventType>}\`; // "onClick" | "onHover"
// Another example with nested objects
type Config = {
mode: {
value: "light" | "dark";
};
};
// WRONG
type ThemeClass = \`theme-\${Config["mode"]["value"]}\`; // Error!
// CORRECT
type ModeValue = Config["mode"]["value"];
type ThemeClass = \`theme-\${ModeValue}\`; // "theme-light" | "theme-dark"This pattern separates the property access from the template literal construction.
Mapped types cannot be used inside template literal interpolations. Apply them separately:
// WRONG - mapped type in template
type Keys = "name" | "age";
type GetterNames = \`get\${Capitalize<{ [K in Keys]: K }[Keys]>}\`; // Error!
// CORRECT - map first, then template
type Keys = "name" | "age";
type Getters = {
[K in Keys as \`get\${Capitalize<K>}\`]: () => string;
};
// Result: { getName: () => string; getAge: () => string; }
// Another approach - iterate union directly
type GetterName<K extends string> = \`get\${Capitalize<K>}\`;
type GetterNames = GetterName<Keys>; // "getName" | "getAge"Mapped types work well with template literals using the \as\ clause for key remapping, but not inside \${} interpolations.
Use TypeScript's built-in string transformation utilities correctly:
// Available intrinsic types (since TypeScript 4.1)
type Upper = Uppercase<"hello">; // "HELLO"
type Lower = Lowercase<"WORLD">; // "world"
type Cap = Capitalize<"typescript">; // "Typescript"
type Uncap = Uncapitalize<"Hello">; // "hello"
// Combining them in template literals
type EventName = "click" | "focus" | "blur";
type HandlerName = \`on\${Capitalize<EventName>}\`;
// "onClick" | "onFocus" | "onBlur"
// Chaining transformations
type Method = "getUserData" | "setUserData";
type SnakeCase<T extends string> = Lowercase<T>;
type ApiEndpoint<T extends string> = \`/api/\${SnakeCase<T>}\`;
type Endpoint = ApiEndpoint<Method>;
// "/api/getuserdata" | "/api/setuserdata"
// Complex example - converting camelCase to kebab-case (requires helper)
type KebabCase<S extends string> = S extends \`\${infer A}\${infer B}\`
? B extends Uncapitalize<B>
? \`\${Lowercase<A>}\${KebabCase<B>}\`
: \`\${Lowercase<A>}-\${KebabCase<B>}\`
: S;
type CssClass = KebabCase<"backgroundColor">; // "background-color"These four intrinsic types are the only built-in transformations allowed in template literals.
### Template Literal Type Recursion
Template literal types can be used recursively for complex string parsing and transformation:
\\\typescript
// Parse path parameters from route string
type ExtractParams<T extends string> =
T extends \\${infer Start}:\${infer Param}/\${infer Rest}\
? Param | ExtractParams<\/\${Rest}\>
: T extends \\${infer Start}:\${infer Param}\`
? Param
: never;
type Route = "/users/:userId/posts/:postId";
type Params = ExtractParams<Route>; // "userId" | "postId"
\\\`
Important: Recursive template literal types have depth limits (around 50 levels) to prevent infinite loops. Use them judiciously for parsing tasks.
### Combining with Conditional Types
While you cannot put conditional types inside \${}, you can use them in the broader type definition:
\\\typescript
// Pattern: conditional wrapping the entire template literal
type Prefix<T extends string> = T extends \_\${infer _}\
? T // Already has prefix
: \_\${T}\`; // Add prefix
type Result1 = Prefix<"test">; // "_test"
type Result2 = Prefix<"_test">; // "_test" (not "__test")
\\\`
### Performance Considerations
Complex template literal types with large unions can significantly slow compilation:
\\\typescript
// This creates 1000 × 1000 = 1,000,000 type combinations!
type ThousandNumbers = "0" | "1" | "2" /* ... */ | "999";
type Combinations = \\${ThousandNumbers}-\${ThousandNumbers}\;
\\\
Best practices:
- Limit union sizes in template literals (keep under 100 members when possible)
- Cache intermediate types to reduce recalculation
- Use simpler patterns when union explosion occurs
### Real-World Use Cases
1. Type-safe CSS-in-JS:
\\\typescript
type CSSUnit = "px" | "em" | "rem" | "%";
type CSSValue<T extends number> = \\${T}\${CSSUnit}\`;
type Spacing = CSSValue<4 | 8 | 16 | 32>;
const margin: Spacing = "16px"; // Valid
const padding: Spacing = "20px"; // Error: not in union
\\\`
2. Type-safe event handling:
\\\typescript
type DOMEvent = "click" | "focus" | "blur" | "change";
type EventHandler = \on\${Capitalize<DOMEvent>}\;
type Props = {
[K in EventHandler]?: () => void;
};
// Result: { onClick?: () => void; onFocus?: () => void; ... }
\\\
3. API route type safety:
\\\typescript
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type Resource = "users" | "posts" | "comments";
type Endpoint = \\${Uppercase<HTTPMethod>} /api/\${Resource}\`;
const route: Endpoint = "GET /api/users"; // Valid
const invalid: Endpoint = "PATCH /api/auth"; // Error
\\\`
### Debugging Template Literal Types
To see what a complex template literal type resolves to:
\\\`typescript
// Hover over 'Result' in your IDE to see the expanded type
type Result = YourComplexTemplateLiteralType;
// Or use a type assertion to check
const test: YourComplexTemplateLiteralType =
"expected-value" as const; // See if it type-checks
\\\`
Use \tsc --explainFiles\ and hover tooltips in VS Code to inspect complex type expansions.
Function expression requires a return type
Function expression requires a return type
Value of type 'string | undefined' is not iterable
How to fix "Value is not iterable" in TypeScript
Type 'undefined' is not assignable to type 'string'
How to fix "Type undefined is not assignable to type string" in TypeScript
Type narrowing from typeof check produces 'never'
How to fix "Type narrowing produces never" in TypeScript
Type parameter 'T' has conflicting constraints
How to fix "Type parameter has conflicting constraints" in TypeScript