get / set accessors
Defines a property backed by code, with a setter that may accept wider types than the getter returns.
class C {
get name(): ReadType { }
set name(value: WriteType) { }
}
interface I {
get name(): ReadType;
set name(value: WriteType);
}- Added in TypeScript 1.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$value | WriteType | The setter's single parameter — the value being assigned. Since TypeScript 4.3 it may be wider than the getter's return type, provided the read type is assignable to it. If left unannotated it is inferred from the getter's return type. |
Return value
ReadTypeWhatever the getter returns; this is the type callers see when reading the property.
Errors
A getter whose return type is not assignable to its setter's parameter type is an error, because a property must always be assignable to itself. Assigning to a property that has only a getter is also an error, since it is treated as readonly.
Changelog
- PHP 4.3Getters and setters may declare different types, and interfaces and object types may declare get and set separately. The getter's type must remain assignable to the setter's.
- PHP 4.4Accessors may be given # private names alongside fields and methods.
An accessor pair makes a property look like data while running code on read, write, or both. TypeScript treats the pair as a single property whose read type is the getter's return type and whose write type is the setter's parameter type.
When to reach for it
- Normalising input on assignment: accept
string | number | Date, store one canonical form, always read back that form. - Derived values that must stay in sync —
fullName,area,isExpired. - Adding validation or logging to an existing public field without changing a single call site.
- A getter with no setter, which TypeScript reports as
readonlyfor free.
What trips people up
The asymmetric-type feature has one rule people hit immediately: the getter's type must be assignable to the setter's. Widening the setter is allowed, narrowing it is not, because obj.x = obj.x has to keep type-checking. Omitting the setter's annotation silently inherits the getter's type rather than defaulting to any. And the big runtime surprise: accessors live on the prototype, so they are not own enumerable properties — Object.keys, spread and JSON.stringify all skip them, and a class whose public surface is entirely getters serialises to {}.
Example
When to use it
- Coerce a string, number or boolean into one canonical stored value on assignment.
- Expose a derived value such as fullName or area that recomputes from other fields on each read.
- Turn an existing public field into a validated property without touching any call site.
Frequently asked questions
Can a getter and setter have different types in TypeScript?
Yes, since TypeScript 4.3 — the setter may accept a wider type than the getter returns, which is how you accept several input forms and always read back one canonical type. The getter's type must remain assignable to the setter's, so you can widen the setter but never narrow it.
Why is my getter missing from JSON.stringify?
Because accessors are defined on the prototype, and JSON.stringify only serialises own enumerable properties. Add a toJSON() method returning a plain object, or copy the values into a literal before serialising.
How do I make a property readonly with a getter?
Declare only the getter and omit the setter — TypeScript treats a get-only accessor as a readonly property and reports an error on any assignment. This works for computed values too, since the getter recomputes on every read.
More examples
Getter-only means readonly, and the assignability rule
Prints 12, then 40, then "true". The recomputation on the second line is the point of a getter: nothing caches area, so changing width changes it. The commented-out setter shows the 4.3 rule — a setter may accept more types than the getter returns, never fewer, because `obj.v = obj.v` must always type-check.
class Rect {
constructor(public width: number, public height: number) {}
// No setter, so `area` is readonly to the type checker.
get area(): number { return this.width * this.height; }
}
const r = new Rect(3, 4);
console.log(r.area);
r.width = 10;
console.log(r.area);
// r.area = 99; Error: Cannot assign to 'area' because it is a read-only property.
class Bad {
#v: string | number = 0;
get v(): string | number { return this.#v; }
// set v(value: number) { this.#v = value; }
// Error: The return type of a 'get' accessor must be assignable to its
// 'set' accessor type. Widening the setter is fine; narrowing it is not.
set v(value: string | number | boolean) { this.#v = String(value); }
}
const b = new Bad();
b.v = true;
console.log(b.v);Declaring separate read and write types on an interface
Prints 300 and {"ms":250}. Because the read type is number, `t.ms + 50` is arithmetic rather than string concatenation even though a string was assigned. The toJSON method is there deliberately: without it the accessor would not appear in the serialised output, since it is a prototype property rather than an own one.
interface Duration {
// read as milliseconds, write as anything convertible
get ms(): number;
set ms(value: number | string);
}
class Timeout implements Duration {
#ms = 0;
get ms(): number { return this.#ms; }
set ms(value: number | string) {
this.#ms = typeof value === "string" ? Number(value.replace(/ms$/, "")) : value;
}
toJSON() { return { ms: this.#ms }; }
}
const t = new Timeout();
t.ms = "250ms";
console.log(t.ms + 50);
console.log(JSON.stringify(t));
Discussion