TC39 Decorators Reference

Stage 3 JavaScript class, method, accessor and field decorator syntax and hooks.

Reference for the TC39 Stage 3 decorators proposal covering class, method, getter, setter, auto-accessor and field decorators, the context object passed to each, and the addInitializer hook. It runs free in your browser on Gera Tools, with nothing uploaded.

Last updated Source: Gera Tools

How are Stage 3 decorators different from the old experimental ones?

The legacy TypeScript and Babel decorators received a property descriptor and could mutate it directly. The Stage 3 decorators instead receive the value being decorated plus a structured context object, and they wire up behaviour by returning a replacement value or calling context.addInitializer. The shapes are not compatible.

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

Targetvalue receivedLegal return
ClassThe class constructorA new constructor (or undefined)
MethodThe method functionA replacement function (or undefined)
GetterThe getter functionA replacement getter (or undefined)
SetterThe setter functionA replacement setter (or undefined)
Auto-accessor{ get, set }{ get?, set?, init? } (or undefined)
FieldundefinedAn 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 element
  • statictrue if it is a static class member
  • privatetrue if it is a private field or method
  • addInitializer(fn) — registers a callback run during construction (instance members) or class definition (class or static members); this is 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.