Mapped type modifiers (+/- readonly, ?)
Adds or removes readonly and optional modifiers as a mapped type copies each property.
{ +readonly [Property in keyof Type]+?: Type[Property] }
{ -readonly [Property in keyof Type]-?: Type[Property] }- Added in TypeScript 2.8
Parameters
| Parameter | Type | Description |
|---|---|---|
$readonly modifieroptional | +readonly | -readonly | readonly= preserved from the source | Placed before the key. +readonly (or bare readonly) adds it, -readonly removes it, omitting it entirely preserves whatever the source had in a homomorphic mapping. |
$optional modifieroptional | +? | -? | ?= preserved from the source | Placed after the key. +? (or bare ?) makes every property optional, -? makes every property required and also strips undefined from the property type, omitting it preserves the source. |
Return value
An object type with the modifiers applied or strippedThe mapped object type with readonly and ? added (+, the default when the prefix is omitted) or removed (-) on every mapped property.
Errors
Nothing raises an error, but -? on a property whose declared type includes undefined removes undefined from the type as well, which can silently change assignability.
Changelog
- PHP 2.1Mapped types could add readonly and ?, but never remove them.
- PHP 2.8The + and - prefixes were added, and Required<T> was added to lib.d.ts as { [P in keyof T]-?: T[P] }.
- PHP 4.3Separate read and write types via get/set accessors gave an alternative to whole-type readonly stripping in some cases.
A mapped type can do more than change value types — it can edit the modifiers. Write readonly before the key or ? after it to add them, and prefix either with - to take them away. A bare readonly is shorthand for +readonly; the explicit + form exists mostly for symmetry and readability.
Subtraction is the reason this exists. Before TypeScript 2.8 there was no way to write Required, because a mapping could only ever pile modifiers on. Required<T> in the standard library is exactly { [P in keyof T]-?: T[P] }, and the Mutable helper that nearly every codebase eventually writes is { -readonly [P in keyof T]: T[P] }.
When to reach for it
Turning a frozen configuration back into something a builder can populate, or taking an all-optional options type and asserting that defaults have been merged in so the rest of the code stops null-checking.
What trips people up
-? does two things, not one. It removes the optional marker and strips undefined out of the property's type. Under strictNullChecks, { a?: string } is really { a?: string | undefined }, so Required gives { a: string } — the undefined is gone too. If a property is genuinely allowed to hold undefined while being required, -? is not what you want.
Omitting a modifier entirely is not the same as writing +: in a homomorphic mapping it preserves whatever the source declared.
Example
type Mutable<T> = { -readonly [K in keyof T]: T[K] };
type MyRequired<T> = { [K in keyof T]-?: T[K] };
type Draft<T> = { +readonly [K in keyof T]+?: T[K] };
type LockedAccount = { readonly id: string; readonly name?: string };
type Unlocked = Mutable<LockedAccount>;
// { id: string; name?: string }
type Complete = MyRequired<LockedAccount>;
// { readonly id: string; readonly name: string }
type Loose = Draft<{ a: number; b: string }>;
// { readonly a?: number; readonly b?: string }When to use it
- Write a Mutable helper to unfreeze a type produced by as const
- Assert that an all-optional options object has had its defaults applied
- Produce a draft type where every field of a strict entity is optional
Frequently asked questions
How do I remove readonly from a type?
Map over it with the minus prefix: type Mutable<T> = { -readonly [K in keyof T]: T[K] }. There is no built-in utility for this, so most codebases define it once. Note it is shallow - nested objects keep their own readonly modifiers.
Why does Required remove undefined from my property type?
Because -? strips undefined as well as the optional marker. Under strictNullChecks an optional property implicitly includes undefined, and -? removes both together. If a property must stay assignable to undefined while being required, declare it explicitly rather than using Required.
What is the difference between readonly and +readonly?
Nothing - + is the default and the bare form means the same thing. The explicit +readonly and +? exist so a mapping can state its intent symmetrically next to -readonly and -?, and because omitting the modifier entirely means "preserve the source", which is a third, different behaviour.
More examples
The two handbook examples
CreateMutable removes readonly but leaves everything else alone, which is the standard escape hatch for a value produced by as const that a builder now needs to write to. Concrete removes optionality; note that name and age come out as plain string and number, not string | undefined, because -? strips undefined at the same time.
type CreateMutable<Type> = {
-readonly [Property in keyof Type]: Type[Property];
};
type LockedAccount = {
readonly id: string;
readonly name: string;
};
type UnlockedAccount = CreateMutable<LockedAccount>;
// { id: string; name: string }
type Concrete<Type> = {
[Property in keyof Type]-?: Type[Property];
};
type MaybeUser = {
id: string;
name?: string;
age?: number;
};
type User = Concrete<MaybeUser>;
// { id: string; name: string; age: number }Combining modifiers with key filtering
The three aliases differ only in their prefixes, and the results show each modifier being edited independently: A keeps fontSize optional, B forces it to number, C makes everything both frozen and optional. The last alias shows modifiers and an as clause working together - private-looking keys are dropped while the survivors are unfrozen in the same pass.
// remove readonly only, keep optionality intact
type Mutable<T> = { -readonly [K in keyof T]: T[K] };
// remove both, the classic "give me a plain writable object"
type MutableRequired<T> = { -readonly [K in keyof T]-?: T[K] };
// add both
type ReadonlyPartial<T> = { +readonly [K in keyof T]+?: T[K] };
type Settings = { readonly theme: "dark" | "light"; readonly fontSize?: number };
type A = Mutable<Settings>; // { theme: "dark" | "light"; fontSize?: number }
type B = MutableRequired<Settings>; // { theme: "dark" | "light"; fontSize: number }
type C = ReadonlyPartial<Settings>; // { readonly theme: ...; readonly fontSize?: number }
// the modifiers survive key remapping too
type MutablePublic<T> = {
-readonly [K in keyof T as K extends `_${string}` ? never : K]: T[K];
};
Discussion