Key Remapping in TypeScript

Key remapping with the as clause lets you rename keys in a mapped type. Use it to prefix, suffix, or transform property names based on their original values.

6 min read

Key remapping is a feature of TypeScript mapped types that lets you change the names of properties while transforming a type. It uses an as clause inside the mapped type to compute new keys from old ones.

Without key remapping, a mapped type can only change property value types and modifiers. With the as clause, you can rename keys, add prefixes, or even filter out properties entirely, all in one type declaration.

typescripttypescript
type WithPrefix<T, P extends string> = {
  [K in keyof T as `${P}${string & K}`]: T[K];
};
 
interface User {
  name: string;
  age: number;
}
 
type PrefixedUser = WithPrefix<User, "user_">;

PrefixedUser resolves to { user_name: string; user_age: number }. Every key from User gained the user_ prefix. The intersection with string is needed because a key from keyof T can be a string, number, or symbol, and template literal types only accept string-compatible types.

How the as clause works

The as clause sits between the key iterator and the colon in a mapped type. Whatever type the as expression produces becomes the new key name for that property.

typescripttypescript
type Renamed<T> = {
  [K in keyof T as `new_${string & K}`]: T[K];
};

For each key K in T, the as expression computes a new name by prefixing the key with "new_", and uses that as the property name. The value type stays as T[K].

The expression after as must produce a type that is assignable to string, number, or symbol. Template literal types always produce string types, so they are the most common tool used with key remapping.

Filtering keys with never

A powerful use of key remapping is filtering out properties. If the as expression produces the never type for a key, that key is excluded from the result.

typescripttypescript
type RemoveKind<T> = {
  [K in keyof T as K extends "kind" ? never : K]: T[K];
};
 
interface Shape {
  kind: "circle";
  radius: number;
  color: string;
}
 
type ShapeWithoutKind = RemoveKind&lt;Shape&gt;;

ShapeWithoutKind resolves to { radius: number; color: string }. The kind property is excluded because the as clause returned never for it. All other keys pass through unchanged because the conditional returns K for them.

This is different from Omit because it happens inside the mapped type itself. You can filter and transform keys in a single pass, which is useful when the filtering logic is more complex than a simple union of key names.

Event maps from discriminated unions

One of the most practical key remapping patterns is building event handler maps. Start with a discriminated union of event types, each with a kind property. Use key remapping to produce a type where each event kind becomes a property key.

typescripttypescript
type SquareEvent = { kind: "square"; x: number; y: number };
type CircleEvent = { kind: "circle"; radius: number };
 
type EventConfig<Events extends { kind: string }> = {
  [E in Events as E["kind"]]: (event: E) => void;
};
 
type Config = EventConfig&lt;SquareEvent | CircleEvent&gt;;

Config resolves to { square: (event: SquareEvent) =&gt; void; circle: (event: CircleEvent) =&gt; void }. The as clause extracts the kind property from each union member and uses it as the key. The value is a handler function that receives the specific event type.

This pattern scales to any number of event types. Add a TriangleEvent to the union, and the mapped type automatically gains a triangle property with the correct handler type.

Combining key remapping with template literal types

Key remapping and template literal types in TypeScript work together to produce common API patterns. A classic example is generating getter types from a data interface.

typescripttypescript
type Getters&lt;T&gt; = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
 
interface Person {
  name: string;
  age: number;
}
 
type PersonGetters = Getters&lt;Person&gt;;

PersonGetters resolves to { getName: () =&gt; string; getAge: () =&gt; number }. The as clause uses a template literal type to prepend "get" and the Capitalize intrinsic type to uppercase the first letter of each property name. The value side wraps the original type in a function return.

Capitalize uses TypeScript's built-in string manipulation type, applied to the string-intersected key. Capitalize is one of four intrinsic types: Uppercase, Lowercase, Capitalize, and Uncapitalize. They operate on string literal types at the type level and are essential for key remapping.

Key remapping with primitive key types

Key remapping works with any union, not just the keys of an object type. You can iterate over a hardcoded union and produce an object type.

typescripttypescript
type EventNames = "click" | "focus" | "blur";
 
type EventHandlers = {
  [E in EventNames as `on${Capitalize&lt;E&gt;}`]: (event: E) => void;
};

EventHandlers resolves to { onClick: (event: "click") =&gt; void; onFocus: (event: "focus") =&gt; void; onBlur: (event: "blur") =&gt; void }. The source is a simple union of string literals, not keys from an object type. Key remapping is flexible enough to work with any union of string-like types.

Common mistakes

Forgetting the string intersection on keyof. keyof T can include number and symbol keys, but template literal types only accept strings. Intersect the key with string inside the template literal, or use an explicit conditional type to filter out non-string keys. Without the intersection, the compiler reports a type error.

Producing duplicate keys unintentionally. If two source keys remap to the same target key, the later one silently overwrites the earlier one. This can happen with simple prefix logic on keys that already have that prefix. Double-check your remapping logic when the source type has keys that might collide.

Using key remapping where Pick or Omit would be simpler. If you only need to select or remove keys by name, Pick and Omit are more readable than key remapping. Use key remapping when you need to compute new key names or when the filtering logic is conditional. For basic key selection, see Pick utility type in TypeScript and Omit utility type in TypeScript.

Rune AI

Rune AI

Key Insights

  • The as clause in mapped types renames keys: [K in keyof T as NewName]: T[K].
  • Use template literal types with as to add prefixes or suffixes to key names.
  • Return never from the as clause to filter out keys you do not want in the result.
  • Key remapping can iterate over arbitrary unions, not just keyof.
  • Like all mapped types, key remapping is compile-time only.
RunePowered by Rune AI

Frequently Asked Questions

Can key remapping produce duplicate keys?

Yes. If two source keys remap to the same target key, the later one wins, just like in a regular object type. TypeScript does not warn about this, so ensure your remapping logic produces unique keys.

What happens if the as clause produces a non-string type?

TypeScript only accepts string, number, or symbol as remapped keys. If your expression produces something else, the compiler reports an error. Use template literal types or the Capitalize utility to ensure the result is a valid key type.

Does key remapping work at runtime?

No. Key remapping is a compile-time type transformation. The emitted JavaScript has the same runtime property names as in the source type. Key remapping only changes the TypeScript-level type shape.

Conclusion

Key remapping with the as clause turns mapped types from value-only transformers into key-and-value transformers. Rename properties, add prefixes, filter out unwanted keys, and build event maps from discriminated unions, all with the same mapped type syntax plus a single as clause.