TypeScript 4.4Official release-note behavior

TypeScript 4.4 template string index signatures and aliased control-flow analysis.

TypeScript 4.4 expanded index signatures beyond plain string and number keys and taught control-flow analysis to preserve useful narrowing through constant aliases. These two changes solved practical typing problems that still appear in configuration objects, discriminated unions, and framework APIs.

This guide focuses on the behavior documented in the official TypeScript 4.4 release notes and explains where the features help, where they stop helping, and how to model APIs without turning every object into an unrestricted dictionary.

By Kodi A. Cochran · Published and reviewed September 28, 2026. The language features discussed here were introduced in 2021.

Pattern index signatures

TypeScript 4.4 let an index signature describe a pattern instead of every string.

Before TypeScript 4.4, index signatures were primarily built around string and number. That worked for dictionary-like objects, but it was too broad for APIs that wanted to permit a recognizable family of extra properties while still rejecting unrelated names.

Suppose a component accepts ordinary options plus any HTML-style data-* property. A broad [key: string]: unknown signature would allow every misspelled or accidental property, weakening excess-property checking for the entire object. What the API really wants is narrower: permit keys that begin with data-, but keep rejecting other unknown keys.

TypeScript 4.4 made that possible with template string pattern index signatures. A type can now declare an index signature whose key type is a template-string pattern such as `data-${string}`. The pattern describes an infinite family of possible property names without accepting arbitrary strings.

interface Options {
      width?: number;
      height?: number;
    }

    interface OptionsWithDataProps extends Options {
      [name: `data-${string}`]: unknown;
    }

    const ok: OptionsWithDataProps = {
      width: 640,
      "data-testid": "hero",
      "data-owner": "platform"
    };

    const typo: OptionsWithDataProps = {
      width: 640,
      widht: 800
      // error: "widht" is neither declared nor a data-* property
    };
    

The design value is not merely syntactic. It preserves a useful boundary between extensibility and error detection. The object can support a documented extension namespace while TypeScript continues catching properties outside that namespace.

Why the pattern matters

A scoped escape hatch is safer than a global string index signature.

Many TypeScript APIs have two competing requirements. They want a strongly typed core of known properties, but they also need to accept vendor extensions, custom metadata, environment-prefixed settings, CSS variables, test attributes, telemetry tags, or plugin-defined names. A global [key: string] signature makes the object extensible but also tells the compiler that nearly any string key is expected.

A template pattern lets the API reserve an explicit namespace. For example, a telemetry configuration could permit metric-* properties, a plugin system could allow plugin-* keys, or a UI options object could accept data-*. The prefix becomes part of the contract instead of an informal naming convention.

This is especially helpful with excess-property checking. When an object literal is assigned to a narrower interface, TypeScript checks for unexpected properties. Pattern index signatures let an API keep that protection while carving out only the names it intentionally supports.

The value type still matters. Using unknown says the namespace may contain values of different kinds and forces later consumers to narrow them. Using a more specific value type such as string | number makes the extension surface stricter. Choose the value type based on what downstream code can actually handle rather than defaulting everything to any.

Pattern signatures should also be documented. If callers need to know which prefixes are recognized at runtime, a type alone is not enough. The implementation, validation, serialization behavior, and documentation should agree about what those keys mean.

Symbols and unions

TypeScript 4.4 also expanded which infinite-domain key types can appear in index signatures.

The release added support for symbol index signatures. This matters for APIs that intentionally use symbols as keys rather than converting everything to strings. A type can state that arbitrary symbol keys map to a specific value type, making the contract visible to the compiler.

interface Measurements {
      [key: symbol]: number;
    }

    const latency = Symbol("latency");
    const values: Measurements = {};
    values[latency] = 42;
    

TypeScript 4.4 also allowed index signatures whose key type is a union of infinite-domain primitive types such as string, number, symbol, or template-string patterns. The compiler treats such a union as equivalent to multiple index signatures.

That does not mean every union can be used. The feature is designed around key domains that represent an open-ended set of possible property names. A small finite union of literal property names is better represented with explicit properties, mapped types, or another structure that describes those exact keys.

For API authors, the practical rule is simple: use an index signature when the object genuinely supports an open-ended key space. Use explicit properties when the key set is known. Use a template pattern when the key space is open-ended but intentionally namespaced.

Aliased conditions

TypeScript 4.4 could carry narrowing information through a constant boolean alias.

Control-flow analysis is what lets TypeScript narrow a union after a type guard. If code checks typeof value === "string", TypeScript knows that value is a string inside the matching branch. Before 4.4, moving that condition into a separate constant could lose the useful relationship.

TypeScript 4.4 improved this by recognizing certain aliased conditions and discriminants. Store the guard result in a const alias. The value being guarded must have a stable relationship to that check, such as a constant, a readonly property, or a parameter that is not reassigned. These are separate requirements: an arbitrary mutable boolean does not preserve the original guard merely because its current value happens to be true.

function print(value: string | number) {
      const isString = typeof value === "string";

      if (isString) {
        console.log(value.toUpperCase());
      } else {
        console.log(value.toFixed(2));
      }
    }
    

The compiler understands that isString came from a type guard on value. Inside the true branch, value narrows to string; inside the false branch it narrows to number.

The same improvement applies to discriminated unions. Code can extract a discriminant into a constant and still receive narrowing when the compiler can prove the relationship remains stable. This makes validation and branching code easier to refactor without adding casts or repeating conditions solely to satisfy the type checker.

The analysis can also work transitively across several constant conditions. The release notes show that TypeScript can follow a modest chain of aliases rather than stopping at the first boolean. There is still a deliberate depth cutoff; the compiler does not attempt arbitrary symbolic reasoning through an unlimited graph of expressions.

Limits and edge cases

Narrowing depends on the compiler being able to trust that the tested relationship did not change.

The alias improvement is strongest when the condition is held in a const and the values involved are not reassigned in a way that invalidates the relationship. If code mutates the guarded variable or relies on complex state changes, TypeScript may correctly refuse to preserve the narrowing.

This is a feature, not a shortcoming. A compiler should not infer that a condition observed earlier still proves something after the underlying value can change. When narrowing disappears, inspect the mutation and ownership model before reaching for a cast. The loss of narrowing may be highlighting a real ambiguity in the program.

Likewise, a template string index signature is not runtime validation. An interface that permits data-* keys will catch many compile-time mistakes in TypeScript source, but JavaScript callers, parsed JSON, network input, and dynamically constructed objects can still contain arbitrary properties. Validate untrusted runtime input separately.

Pattern signatures also do not replace a thoughtful data model. If callers are creating hundreds of dynamically named fields that need independent lifecycle, querying, or validation, a Map, nested record, or explicit collection may be clearer than encoding the data structure into property names.

When reviewing an older codebase, avoid mechanically replacing all broad string index signatures. Some objects really are general dictionaries. The useful migration target is an interface whose callers follow a prefix convention already, while the type currently accepts far more keys than the runtime or API actually intends.

Migration checklist

Use the TypeScript 4.4 features to make existing intent more explicit.

Start with APIs that contain comments such as “custom keys must begin with data-” or “plugin settings use the vendor- prefix.” Those comments are candidates for a template string pattern index signature. Replace a broad index signature only after confirming the implementation truly rejects or ignores other unknown keys.

Next, look for repeated type guards that were duplicated after refactoring. If a codebase stores a guard in a constant and then uses casts or non-null assertions because an older compiler lost the narrowing, TypeScript 4.4's alias analysis may allow the code to become simpler. Remove assertions one at a time and let the compiler demonstrate which relationships it can prove.

Add type tests for intended and unintended keys. Verify that allowed prefixes compile, ordinary declared properties retain their correct types, misspellings still fail, and runtime validation covers data that does not originate in trusted TypeScript source.

Download the independent TypeScript 4.4 type-checking exercises. The file includes valid pattern and symbol keys, a misspelled key, an incorrect value type, and a mutable guard alias. Expected errors are marked explicitly so a successful compiler run verifies both the examples that should compile and the mistakes it should reject. The file header includes the pinned compiler command.

If your project is already on a much newer TypeScript version, these features are part of the historical language evolution rather than a reason to downgrade or pin to 4.4. The value of understanding them is that they explain why modern TypeScript accepts certain patterns and how to design APIs that take advantage of those capabilities.

For the exact behavior and examples, use the official TypeScript 4.4 release notes. They remain the authoritative reference for the release and include the other changes shipped alongside these features, such as unknown catch variables, exact optional property types, class static blocks, performance work, and breaking changes.

Primary sources

TypeScript's own release documentation defines the feature behavior.

Continue learning

Related guides after TypeScript 4.4 Template String Index Signatures

Follow the next implementation topic without returning to search.

Put this guide to work

Turn TypeScript 4.4 Index Signatures & Control Flow | Zeph Tech into a decision-ready next step.

Use the source-backed research to pressure-test assumptions, then build a reusable evaluation brief before you compare products, scope implementation, or request a fit review.