What TC39 decorators are
Decorators are a Stage 3 JavaScript proposal for annotating and modifying classes
and their members with reusable @decorator syntax. Unlike the older
experimental decorators that mutated a property descriptor, the modern proposal
hands each decorator the value being decorated plus a structured context
object, and you opt into behaviour by returning a replacement value or calling
context.addInitializer. This makes decorators predictable and composable.
How it works
A decorator is a function called once, at class-definition time, with two
arguments: (value, context). The value depends on the target — a function for
methods, the class itself for class decorators, undefined for fields — and the
context always carries kind, name, static, private, and an
addInitializer hook (plus an access object for members). What you may
return also depends on the target:
function logged(value, context) {
if (context.kind === "method") {
return function (...args) {
console.log(`calling ${String(context.name)}`);
return value.apply(this, args);
};
}
}
The reference below lists each target with the value it receives, the relevant context fields, and the legal return values.
Decorator targets at a glance
| Target | value received | Legal return |
|---|---|---|
| Class | The class constructor | A new constructor (or undefined) |
| Method | The method function | A replacement function (or undefined) |
| Getter | The getter function | A replacement getter (or undefined) |
| Setter | The setter function | A replacement setter (or undefined) |
| Auto-accessor | { get, set } | { get?, set?, init? } (or undefined) |
| Field | undefined | An initializer function (or undefined) |
The context object
Every decorator receives a second argument with at least these fields:
kind— one of"class","method","getter","setter","accessor","field"name— a string or Symbol naming the decorated elementstatic—trueif it is a static class memberprivate—trueif it is a private field or methodaddInitializer(fn)— registers a callback run during construction (instance members) or class definition (class or static members);thisis bound to the instance or class
For non-class decorators, context.access also provides get and/or set functions so the decorator can reach the member’s value at runtime without hard-coding the key.
Worked examples
Method decorator — wrap for logging:
function logged(fn, { name }) {
return function (...args) {
console.log(`→ ${String(name)}`);
const result = fn.apply(this, args);
console.log(`← ${String(name)}`);
return result;
};
}
class Service {
@logged
save(data) { /* … */ }
}
Field decorator — transform the initial value:
function trimmed(_, { kind }) {
if (kind === "field") {
return (initial) => typeof initial === "string" ? initial.trim() : initial;
}
}
class Form {
@trimmed
username = " alice "; // stored as "alice"
}
addInitializer — bind a method once at construction:
function bound(fn, { name, addInitializer }) {
addInitializer(function () {
this[name] = fn.bind(this);
});
}
class Button {
@bound
handleClick() { /* … */ }
}
Tips and notes
Decorators evaluate outermost-last: in @a @b method(), b is applied
before a, and a wraps b’s result. Field decorators return an initializer
(value transformer), not a replacement value. The accessor keyword creates an
auto-accessor whose decorator returns an object with get, set, and/or init.
Use addInitializer for side-effects like method binding or metadata
registration — that is the only place this refers to the instance.
TypeScript 5.0+ implements this Stage 3 proposal; use "experimentalDecorators": false (or omit the flag) to get the new semantics. Babel users need @babel/plugin-proposal-decorators with "version": "2023-11" or later.