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.
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.
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.
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.
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<Shape>;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.
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<SquareEvent | CircleEvent>;Config resolves to { square: (event: SquareEvent) => void; circle: (event: CircleEvent) => 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.
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface Person {
name: string;
age: number;
}
type PersonGetters = Getters<Person>;PersonGetters resolves to { getName: () => string; getAge: () => 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.
type EventNames = "click" | "focus" | "blur";
type EventHandlers = {
[E in EventNames as `on${Capitalize<E>}`]: (event: E) => void;
};EventHandlers resolves to { onClick: (event: "click") => void; onFocus: (event: "focus") => void; onBlur: (event: "blur") => 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
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.
Frequently Asked Questions
Can key remapping produce duplicate keys?
What happens if the as clause produces a non-string type?
Does key remapping work at runtime?
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.
More in this topic
TypeScript Compiler Explained
The TypeScript compiler (tsc) turns .ts files into .js files while checking types. Learn what it does, how to use it, and how it fits your workflow.
Set Up ESLint for TypeScript
Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.
Pick Utility Type in TypeScript
Pick creates a new type by selecting only the properties you name from an existing type. Use it to build focused types for specific views, API responses, or function parameters.