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.
Master typescript decorators
Take the TypeScript Architecture & Patterns course with hands-on lessons and challenges.
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.
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.
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.
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.
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.
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)).
Enabling `experimentalDecorators` in tsconfig and expecting the standard decorator API — legacy and standard decorators have incompatible signatures
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.
Decorating a plain class field and expecting get/set interception — standard field decorators without `accessor` can only observe the initial value
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.
Using an arrow function in the replacement wrapper, which loses the class instance `this` binding
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.
Trying to use parameter decorators with the standard API — they do not exist in the TC39 proposal
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.
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.
Interactive lessons and challenges on Stanza, practice in VS Code, Cursor, or the web.
Interactive lessons and challenges, right in your code editor.
Check the free courses. No credit card.