Topics

Jump to a topic.

Search questions

Find a question by title, tag, or summary.

How do decorators work in TypeScript?

Questions in this chapter (6)
  • Draft
  • hard
  • Askedoften
  • Verified October 2026

Answer

A decorator is a function applied to a class or class element when the class is defined. It can observe, replace, or add initialisation logic. Conceptually @dec turns member into dec(member, context) ?? member.

Standard decorators (TS 5.0+, no flag). Signature (value, context). value is the method, class, getter/setter, or undefined for a field. context has kind ("method", "field", "class", "accessor", ...), name, static, private, addInitializer, and since 5.2 metadata. Return a replacement of the same kind (a function for a method, an initializer function for a field) or nothing. TS types each kind with ClassMethodDecoratorContext, ClassFieldDecoratorContext, and so on. accessor x = 1 is the proposal's auto-accessor keyword that decorators can wrap.

Legacy decorators (experimentalDecorators). Based on the old Stage 2 design: (target, key, descriptor), parameter decorators, and emitDecoratorMetadata (used by dependency-injection frameworks). They are a separate type-check and a separate emit. The 5.0 notes say existing legacy decorators are unlikely to work as standard ones, and standard ones do not support parameter decorators or emitDecoratorMetadata.

Status. The proposal repository README says Stage 2.7, and the tc39/proposals page lists it under Stage 2.7 (fetched 2026-10). TS 5.0 implemented the then-Stage-3 version (PR 50820). ❓ I did not diff today's proposal text against TypeScript's implementation.

Runtime. Native Node 24 cannot parse @dec (SyntaxError, checked), so the code needs TypeScript, Babel, or a bundler. TS downlevels with __esDecorate/__runInitializers helpers for --target es2022, while --target esnext kept the @ syntax in the output (7.0.2).

Example

// TypeScript 7.0.2 and 5.9.3, --strict --target es2022 (standard decorators)
function logged<This, Args extends unknown[], R>(
  method: (this: This, ...args: Args) => R,
  context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => R>,
) {
  return function (this: This, ...args: Args): R {
    console.log(`enter ${String(context.name)}`);
    return method.call(this, ...args);
  };
}
function atLeast(min: number) {
  return function <This>(_: undefined, context: ClassFieldDecoratorContext<This, number>) {
    return (initial: number) => Math.max(min, initial); // replaces the field's initializer
  };
}
class Counter {
  @atLeast(0) count = -5;
  @logged inc(by: number) { this.count += by; return this.count; }
}
console.log(new Counter().inc(2));
// → enter inc
// → 2

@logged class Wrong {}
// Error TS1238: Unable to resolve signature of class decorator when called as an expression.

Pitfalls

  • Mixing the two systems. Without experimentalDecorators, a three-argument legacy decorator fails on both versions with TS1241 ("The runtime will invoke the decorator with 2 arguments, but the decorator expects 3") and a parameter decorator gives TS1206.
  • Typing the decorator loosely. logged above applied to a class or a field is a type error (TS1238 / TS1240) because context is typed ClassMethodDecoratorContext; a decorator typed with any would not catch the misuse.
  • Expecting decorators to run per call or per instance. They run once at class definition; per-instance work goes in context.addInitializer or a returned field initializer.
  • Assuming reflect-metadata is built in. emitDecoratorMetadata is legacy-only, and __decorate calls Reflect.decorate only when something has defined it.

Follow-ups

In what order do they run?
Checked on 7.0.2: for @d("A") @d("B") m() {} the output was eval A, eval B, apply B, apply A. Decorator expressions evaluate top to bottom; application is bottom to top, matching the 5.0 notes.
What is context.metadata?
TS 5.2's decorator metadata: a shared object per class, readable afterwards via Class[Symbol.metadata]. Node 24 printed Symbol.metadata as undefined, so I polyfilled it; with that, two @tag field decorators produced [ 'name:req', 'age:opt' ].