Mapped Types
Mapped types transform object types by iterating over their keys and producing new property types.
1type O = {foo: number, bar: string};2type ReadOnly = {+[key in keyof O]: O[key]};When to use this
Use mapped types when you need to systematically transform every property of an existing object type — for example, making all properties optional, read-only, or wrapping their values. If you only need to change a few specific properties, object type spread is simpler.
Basic Usage
Mapped types have syntax similar to indexed object types but use the in keyword:
1type O = {foo: number, bar: string};2
3type Methodify<T> = () => T;4
5type MappedType = {[key in keyof O]: Methodify<O[key]>};In this example, MappedType has all of the keys from O with all of the value types transformed by
Methoditfy<O[key]>. The key variable is substituted for each key in O when creating the property, so
this type evaluates to:
{
foo: Methodify<O['foo']>,
bar: Methodify<O['bar']>,
}
= {
foo: () => number,
bar: () => string,
}
Mapped Type Sources
We call the type that comes after the in keyword the source of the mapped type. The source of
a mapped type must be a subtype of string | number | symbol:
1type MappedType = {[key in boolean]: number}; // ERROR!incompatible-typeCannot instantiate mapped type [1] because boolean [2] is incompatible with string | number | symbol, so it cannot be used to generate keys for mapped type [1].Typically, you'll want to create a mapped type based on another object type. In this case, you
should write your mapped type using an inline keyof:
1type GetterOf<T> = () => T;2type Obj = {foo: number, bar: string};3type MappedObj = {[key in keyof Obj]: GetterOf<Obj[key]>};NOTE:
keyofonly works inline in mapped types for now. Full support forkeyofis not yet available.
But you do not need to use an object to generate a mapped type. You can also use a union of string literal types to represent the keys of an object type:
1type Union = 'foo' | 'bar' | 'baz';2type MappedType = {[key in Union]: number};3// = {foo: number, bar: number, baz: number};Importantly, when using string literals the union is collapsed into a single object type:
1type MappedTypeFromKeys<Keys extends string> = {[key in Keys]: number};2type MappedFooAndBar = MappedTypeFromKeys<'foo' | 'bar'>;3// = {foo: number, bar: number}, not {foo: number} | {bar: number}If you use a type like number or string in the source of your mapped type then Flow will create
an indexer:
1type MappedTypeFromKeys<Keys extends string> = {[key in Keys]: number};2type MappedFooAndBarWithIndexer = MappedTypeFromKeys<'foo' | 'bar' | string>;3// = {foo: number, bar: number, [string]: number}Distributive Mapped Types
When the mapped type uses an inline keyof or a type parameter bound by keyof
Flow will distribute the mapped type over unions of object types.
For example:
1// This mapped type uses keyof inline2type MakeAllValuesNumber<O extends {...}> = {[key in keyof O]: number};3type ObjWithFoo = {foo: string};4type ObjWithBar = {bar: string};5
6type DistributedMappedType = MakeAllValuesNumber<7 | ObjWithFoo8 | ObjWithBar9>; // = {foo: number} | {bar: number};10
11// This mapped type uses a type parameter bound by keyof12type Pick<O extends {...}, Keys extends keyof O> = {[key in Keys]: O[key]};13type O1 = {foo: number, bar: number};14type O2 = {bar: string, baz: number};15type PickBar = Pick<O1 | O2, 'bar'>; // = {bar: number} | {bar: string};Distributive mapped types will also distribute over null and undefined:
1type Distributive<O extends ?{...}> = {[key in keyof O]: O[key]};2type Obj = {foo: number};3type MaybeMapped = Distributive<?Obj>;// = ?{foo: number}4null as MaybeMapped; // OK5undefined as MaybeMapped; // OK6({foo: 3}) as MaybeMapped; // OKProperty Modifiers
You can also add + or - variance modifiers and the optionality modifier ? in mapped types:
1type O = {foo: number, bar: string}2type ReadOnlyPartialO = {+[key in keyof O]?: O[key]}; // = {+foo?: number, +bar?: string};When no variance nor optionality modifiers are provided and the mapped type is distributive, the variance and optionality are determined by the input object:
1type O = {+foo: number, bar?: string};2type Mapped = {[key in keyof O]: O[key]}; // = {+foo: number, bar?: string}Otherwise, the properties are read-write and required when no property modifiers are present:
1type Union = 'foo' | 'bar' | 'baz';2type MappedType = {[key in Union]: number};3// = {foo: number, bar: number, baz: number};NOTE: Flow does not yet support removing variance or optionality modifiers.
Mapped Type on Arrays ≥0.246
Mapped type also works on array or tuple inputs. If the mapped type is in the form of
{[K in keyof <type_1>]: <type_2>}
then type_1 is allowed to be an array or tuple type.
This feature will be especially useful if you want to map over elements of a tuple:
1type Tuple = [+a: number, b?: string];2type MappedTuple = {[K in keyof Tuple]: Tuple[K] extends number ? boolean : string};3const a: MappedTuple[0] = true;4const b: MappedTuple[1] = '';5'' as MappedTuple[0] // errorincompatible-typeCannot cast empty string to indexed access because string [1] is incompatible with boolean [2].6false as MappedTuple[1] // errorincompatible-typeCannot cast false to indexed access because boolean [1] is incompatible with string [2].7declare const mapped: MappedTuple;8mapped[0] = true; // error: cannot-writecannot-writeCannot assign true to mapped[0] because tuple element at index 0 [1] labeled a is not writable.For now, the only supported property modifier on array input is the optionality modifier ?.
1type Tuple = [+a: number, b?: string];2type Supported = {[K in keyof Tuple]?: string};3type Unsupported1 = {+[K in keyof Tuple]: string}; // Errorinvalid-mapped-typeMapped Types do not yet support variance annotations on array inputs.4type Unsupported2 = {-[K in keyof Tuple]: string}; // Errorinvalid-mapped-typeMapped Types do not yet support variance annotations on array inputs.Adoption
To use mapped types, you need to upgrade your infrastructure so that it supports the syntax:
flowandflow-parser: 0.210.0. Between v0.210.0 to v0.211.1, you need to explicitly enable it in your .flowconfig, under the[options]heading, addmapped_type=true.prettier: 3babelwithbabel-plugin-syntax-hermes-parser. See our Babel guide for setup instructions.eslintwithhermes-eslint. See our ESLint guide for setup instructions.
See Also
- Indexed Access Types — extracting property types, commonly used in mapped type bodies
- Conditional Types — another advanced type feature for type-level logic
- Generics — mapped types rely on generic type parameters
- Utility Types — built-in types like
Partial,Readonly, andPick(which mapped types generalize)