Introduction
TypeScript 4.1 introduced key remapping in mapped types via the as clause. This lets you rename, transform, or filter keys during mapping — a capability that was previously impossible without complex workarounds. Key remapping is how you build types like Getters<T>, event handler maps, and filtered property subsets.
Key Concepts
asclause: In[K in keyof T as NewKey], theas NewKeyexpression remaps each key to a new name.- Filtering with
never: Returningneverfrom theasclause removes that key entirely. - Template literal keys: Combine
aswith template literals to transform key names (e.g.,as \get${Capitalize<K>}``).
Real World Context
Key remapping is used in production code for generating API client methods from endpoint definitions, creating getter/setter types from state shapes, building event handler types from event name lists, and filtering object types to only include properties of a certain type. It is one of the most practically useful advanced type features.
Deep Dive
Basic Key Remapping with as
The as clause transforms the key during iteration:
typescripttype Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]; }; interface Person { name: string; age: number; } type PersonGetters = Getters<Person>; // { getName: () => string; getAge: () => number }
The string & K intersection is needed because keyof T could include symbol keys, and Capitalize only works on strings.
Filtering Keys with never
Returning never from the as clause removes a key from the output:
typescript// Keep only string-valued properties type OnlyStrings<T> = { [K in keyof T as T[K] extends string ? K : never]: T[K]; }; interface Mixed { name: string; age: number; email: string; active: boolean; } type StringProps = OnlyStrings<Mixed>; // { name: string; email: string }
The conditional T[K] extends string ? K : never evaluates for each property. Properties whose values are not strings produce never as the key, which removes them from the mapped type.
Remapping with Exclude
You can use Exclude in the as clause to remove specific keys:
typescripttype OmitImplementation<T, K extends keyof T> = { [P in keyof T as Exclude<P, K>]: T[P]; }; type WithoutAge = OmitImplementation<Person, "age">; // { name: string }
This is actually how the built-in Omit<T, K> could be implemented using key remapping.
Building a Prefix Map
Key remapping is powerful for generating prefixed or transformed APIs:
typescripttype PrefixedAPI<T, Prefix extends string> = { [K in keyof T as `${Prefix}${Capitalize<string & K>}`]: T[K]; }; interface CRUD { create: (data: unknown) => void; read: (id: string) => unknown; update: (id: string, data: unknown) => void; delete: (id: string) => void; } type UserAPI = PrefixedAPI<CRUD, "user">; // { userCreate: ...; userRead: ...; userUpdate: ...; userDelete: ... }
This pattern is used in code generators that produce typed client SDKs from API definitions.
Common Pitfalls
- Forgetting
string &beforeCapitalize—Capitalize<K>fails whenKmight be asymbol. Always useCapitalize<string & K>to narrow to string keys only. - Expecting key remapping to preserve modifiers — Key remapping creates a fresh mapped type. If the original properties were
readonlyor optional, you need to explicitly preserve those modifiers.
Best Practices
- Use key remapping for clean API surface types — Instead of manual interface declarations, derive API types from a base definition using
aswith template literals. - Combine filtering and renaming in one mapped type — You can both filter (return
neverfor unwanted keys) and rename (use template literals) in the sameasclause.
Summary
- The
asclause in mapped types remaps keys to new names. - Return
neverfromasto filter out unwanted properties. - Template literals in
asenable key name transformations like prefixing and capitalizing. - Always use
string & Kwhen applying string manipulation utility types to mapped type keys.
Code Examples
// Filter + rename in a single mapped type
type EventEmitter<T> = {
[K in keyof T as T[K] extends (...args: any[]) => any
? `on${Capitalize<string & K>}`
: never
]: T[K];
};
interface AppEvents {
click: (x: number, y: number) => void;
title: string; // not a function — will be filtered out
resize: (w: number, h: number) => void;
version: number; // not a function — will be filtered out
}
type Emitter = EventEmitter<AppEvents>;
// {
// onClick: (x: number, y: number) => void;
// onResize: (w: number, h: number) => void;
// }