How to fix Template literal substitution type must be literal or union in TypeScript

TypeScriptINTERMEDIATELOWUpdated Dec 11, 2025· 130 views

This compiler error means a template literal type is trying to interpolate a placeholder whose type is too broad—usually just `string` or `any`. Template literal placeholders only resolve when they are literal string unions, so the fix is to feed the template a finite set of string literals instead of a wide type.

Quick fix

Fixes Template literal substitution type must be literal or union

type Prefixed<T extends string> = `prefix:${T}`;
type Bad = Prefixed<string>; // ❌ Template literal substitution type must be literal or union
  1. Spot which placeholder is too wide
  2. Feed the template a literal union
  3. Turn runtime values into literal unions
  4. Extract literal keys from objects
typescript — error
type Prefixed<T extends string> = `prefix:${T}`;
type Bad = Prefixed<string>; // ❌ Template literal substitution type must be literal or union
Template literal substitution type must be literal or union

What this error means

Template literal types concatenate string literal types at the type level. Each `${...}` placeholder must resolve to a literal or union of literals so the compiler can enumerate the final set of acceptable strings. If the placeholder is a plain `string`, `any`, or some type that widens from runtime data, the compiler cannot know which literal values will appear, so it raises "Template literal substitution type must be literal or union". The error is a safeguard that keeps your template literal types expressive but tractable.

Typical symptoms

  • tsc reports "Template literal substitution type must be literal or union" on a template literal type declaration
  • You are building a string via ``${...}`` inside a type and the type parameter is inferred as just `string` or `any`
  • Errors disappear when you replace the placeholder with a literal union such as `'draft' | 'published'`
  • IntelliSense squiggles run along the template literal definition (not necessarily the usage site)
  • The problem surfaces when you derive the placeholder from `Object.keys`, runtime arrays, or untyped `Record<string, ...>` values

Common causes

  1. Passing a generic type parameter of `string`, `any`, or another wide primitive into `${...}`
  2. Collecting keys via `Object.keys` or `Record<string, ...>` without narrowing them to literal unions
  3. Assuming template literal types will automatically infer a union from runtime constants without `as const`
  4. Using template literal type helpers inside mapped types where the input is not constrained to literal strings
  5. Letting `const name: string` widen from a function parameter before building the template literal

How to fix "Template literal substitution type must be literal or union"

1Spot which placeholder is too wide

The compiler error points to the template literal that wraps the substitution. Identify the placeholder whose type is widening beyond literal values:

ts
type Prefixed<T extends string> = `prefix:${T}`;
type Bad = Prefixed<string>; // ❌ Template literal substitution type must be literal or union

In this example, T can be any string, so TypeScript refuses to expand the template. The fix is to ensure T represents a finite union of literal strings instead of the open-ended string type.

2Feed the template a literal union

Replace the placeholder with a known union so the template declares exactly which strings can appear:

ts
type Status = "/draft" | "/published";
type StatusPath<S extends Status> = `/status${S}`;
type Draft = StatusPath<"/draft">; // ✅ emits "/status/draft"

As soon as S is a literal union, the error vanishes because the compiler can enumerate the resulting strings.

3Turn runtime values into literal unions

When you build unions from runtime data, lock them to literal types with as const or helper functions:

ts
const events = ["created", "updated", "deleted"] as const;
type EventName = (typeof events)[number]; // "created" | "updated" | "deleted"
type Topic<E extends EventName> = `${E}-event`;

as const tells TypeScript to treat each string as a literal, so events[number] stays a union instead of widening to string. Without the assertion you would still see the substitution error because the placeholder would be string again.

4Extract literal keys from objects

Measured objects like Record<string, ...> or enums default to string, so derive a literal union with keyof typeof or Extract:

ts
const routes = {
  home: "/",
  dashboard: "/dashboard",
} as const;

type RouteKey = keyof typeof routes; // "home" | "dashboard"
type RoutePath<K extends RouteKey> = `${routes[K]}?section`;

Now the placeholder is constrained to RouteKey, which is a literal union, and the template compiles without error.

5Double-check helpers that return `string`

Helpers such as Object.keys, Object.entries, and Array.prototype.map widen to string[]. Wrap them in a typed helper or as const so they return literal tuples:

ts
function literalKeys<T extends readonly string[]>(...keys: T) {
  return keys;
}

const methodNames = literalKeys("getUser", "setUser");
type MethodName = typeof methodNames[number]; // "getUser" | "setUser"
type RpcCall<M extends MethodName> = `${M}Rpc`;

When the placeholder comes from MethodName, the template literal resolves cleanly.

6Confirm TypeScript accepts the change

Run the compiler and observe that the template literal error disappears:

bash
npx tsc --noEmit

If the error is gone, IntelliSense and incremental builds will represent the literal union you intended instead of falling back to string. Keep testing with the combinations of literals you support to validate future changes.

Advanced notes

### Why the compiler insists on literal or union placeholders

Template literal types produce explicit unions of string literals. When a placeholder resolves to string, number, boolean, or bigint, TypeScript defaults the entire template to string, which defeats the purpose of capturing a finite set of outcomes. The error prevents this fallback, forcing you to model the allowed strings with literal unions or helper types.

### Const assertions, satisfies, and branded helpers

as const and the satisfies operator keep runtime arrays from widening to string[]. For example, const states = ["draft", "published"] as const satisfies readonly typeof states[number][]; gives you a literal union while still allowing the helper to validate runtime inputs.

### When you really need to accept any string

If you legitimately want a template literal to accept any string, return the primitive string instead of using a template literal type, or explicitly intersect with string:

ts
type AnyPath = `${string}/path`; // resolves to string

Because the placeholder is string, the compiler doesn't throw the literal-or-union error, but also doesn't preserve leftovers like section in the resulting type, so reserve this pattern for when you do not need the extra type safety.

Frequently asked questions

What does "Template literal substitution type must be literal or union" mean?
Template literal types concatenate string literal types at the type level. } placeholder must resolve to a literal or union of literals so the compiler can enumerate the final set of acceptable strings. If the placeholder is a plain string, any, or some type that widens from runtime data, the compiler cannot know which literal values will appear, so it raises "Template literal substitution type must be literal or union".
How do I fix "Template literal substitution type must be literal or union"?
This compiler error means a template literal type is trying to interpolate a placeholder whose type is too broad—usually just string or any. Template literal placeholders only resolve when they are literal string unions, so the fix is to feed the template a finite set of string literals instead of a wide type.
What causes "Template literal substitution type must be literal or union"?
Passing a generic type parameter of string, any, or another wide primitive into ${...} Collecting keys via Object.keys or Record<string, ...> without narrowing them to literal unions Assuming template literal types will automatically infer a union from runtime constants without as const
Is the "Template literal substitution type must be literal or union" error serious?
It is a low-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading