Mapped type modifiers (+/- readonly, ?)

Adds or removes readonly and optional modifiers as a mapped type copies each property.

Signature
{ +readonly [Property in keyof Type]+?: Type[Property] }
{ -readonly [Property in keyof Type]-?: Type[Property] }
  • Added in TypeScript 2.8

Parameters

ParameterTypeDescription
$readonly modifieroptional+readonly | -readonly | readonly= preserved from the sourcePlaced 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 sourcePlaced 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

Example · typescript
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.

Example · typescript
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.

Example · typescript
// 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

  • Be the first to comment on this lesson.