TypeScript

TypeScript Decorators👨‍💻

Decorators attach reusable behavior to classes, methods, and fields using a declarative @ syntax. TypeScript 5.0 shipped the TC39 Stage 3 standard implementation, which works out of the box with no tsconfig flags. If you've used decorators in Angular or NestJS, those are the legacy experimental API — the standard version has a different signature, built-in metadata, and no dependency on reflect-metadata.

Key Takeaways

  • 1Standard decorators (TC39 Stage 3) require no `experimentalDecorators` flag — they work by default in TypeScript 5.0+
  • 2Every decorator receives two arguments: the value being decorated and a context object (`ClassDecoratorContext`, `ClassMethodDecoratorContext`, etc.)
  • 3Method decorators return a replacement function that wraps the original — this is how you build logging, caching, and authorization wrappers
  • 4Field decorators require the `accessor` keyword to get full get/set interception via `ClassAccessorDecoratorContext`
  • 5Built-in metadata via `context.metadata` replaces the `reflect-metadata` polyfill — read it back at runtime through `Symbol.metadata`
  • 6Parameter decorators are NOT supported in the standard API — frameworks like NestJS and Angular still rely on the legacy experimental API until they migrate

Master typescript decorators

Take the TypeScript Architecture & Patterns course with hands-on lessons and challenges.

Examples

Class decorator — register a controller

typescript

A class decorator factory that writes the route prefix to metadata. At bootstrap time, your framework reads Symbol.metadata to build the routing table. No reflect-metadata import needed.

Method decorator — logging with timing

typescript

The decorator wraps the original method, adding timing and argument logging. Notice the regular function (not arrow) to preserve `this`. In production, you'd gate this behind an environment check or log level.

Method decorator factory — memoization with TTL

typescript

A decorator factory that accepts a TTL in milliseconds. The outer function is the factory, the middle function is the decorator, and the inner function is the replacement method. This three-layer pattern is how every configurable decorator works.

Auto-accessor decorator — field validation

typescript

The `accessor` keyword turns a field into a getter/setter pair that decorators can intercept. The decorator returns an object with `init`, `get`, and `set` — giving you validation on both initialization and assignment. Without `accessor`, field decorators have very limited capabilities.

Metadata — building a route registry

typescript

Multiple decorators on different methods write to the same metadata object. At startup, a framework reads `Symbol.metadata` to build the routing table. This is exactly how frameworks like Express routers and NestJS controllers work under the hood — the standard API just removes the reflect-metadata dependency.

Composing decorators — stacking multiple behaviors

typescript

Decorators compose bottom-up: `@rateLimit` wraps the method first, then `@auth` wraps that result. When called, `auth` executes first (outermost wrapper), then `rateLimit`. This is the same evaluation order as function composition: auth(rateLimit(original)).

Common Mistakes

Mistake:

Enabling `experimentalDecorators` in tsconfig and expecting the standard decorator API — legacy and standard decorators have incompatible signatures

Fix:

Remove `experimentalDecorators` from your tsconfig to use the TC39 standard API. When that flag is on, TypeScript treats ALL decorators as legacy `(target, key, descriptor)` style. You cannot mix both in the same project.

Mistake:

Decorating a plain class field and expecting get/set interception — standard field decorators without `accessor` can only observe the initial value

Fix:

Use the `accessor` keyword: `@validate accessor email = ""`. This creates a getter/setter pair that your decorator can wrap, giving you full control over reads and writes.

Mistake:

Using an arrow function in the replacement wrapper, which loses the class instance `this` binding

Fix:

Always return a regular `function` expression from a method decorator: `return function(this: any, ...args) { ... }`. Arrow functions capture the outer `this`, which is not the class instance.

Mistake:

Trying to use parameter decorators with the standard API — they do not exist in the TC39 proposal

Fix:

If you need parameter-level decoration (common in DI frameworks), you must use the legacy `experimentalDecorators` API for now, or restructure to use method-level decorators with metadata to track parameter information.

Best Practices

  • Start new projects with standard decorators (no tsconfig flag) — the TC39 API is the future, and legacy `experimentalDecorators` will eventually be deprecated
  • Use decorator factories for anything configurable: `@throttle(300)` is one function returning another, giving callers control over behavior without touching the decorator internals
  • Type the context parameter with the specific context type (`ClassMethodDecoratorContext`, `ClassAccessorDecoratorContext`) — it narrows what properties are available and catches misuse at compile time
  • Keep decorators small and composable — a `@logged` decorator and a `@cached` decorator that stack cleanly are more useful than a single `@loggedAndCached` monolith
  • Document the evaluation order when stacking decorators: they apply bottom-up (inner to outer) but execute top-down (outer to inner) — this trips up even experienced developers

Summary

TypeScript 5.0 shipped TC39 Stage 3 standard decorators, which work without any tsconfig flag and replace the legacy experimental API. Standard decorators receive `(value, context)` instead of `(target, key, descriptor)`, support built-in metadata via `context.metadata` (no reflect-metadata needed), and use the `accessor` keyword for field interception. They do not support parameter decorators. Use them for cross-cutting concerns — logging, caching, validation, authorization, route registration — anywhere you want to separate infrastructure behavior from business logic.

Practice TypeScript with hands-on challenges

Learn typescript decorators hands-on in your IDE

Interactive lessons and challenges on Stanza, practice in VS Code, Cursor, or the web.

Related Concepts

Related Cheatsheets

Master TypeScript with Stanza

Interactive lessons and challenges, right in your code editor.

Check the free courses. No credit card.