TypeScript 5.0 (March 2023) shipped support for the TC39 Stage 3 ECMAScript decorators proposal as the new default — no compiler flag required. This replaced the older, pre-standard "experimental decorators" TypeScript had supported since 2015 (requiring experimentalDecorators: true), which some established frameworks (Angular, NestJS, TypeORM) still rely on as of 2026 during their own migration. The two are genuinely incompatible: they use different function signatures, and modern Stage 3 decorators notably dropped parameter decorators, which the legacy dependency-injection-heavy frameworks depended on heavily.
Modern (Stage 3) decorators: the default since TypeScript 5.0
A modern method decorator receives the original method plus a typed context object (ClassMethodDecoratorContext, etc.) and returns a replacement function — no flag needed in tsconfig.json for this style.
function logged(target: Function, context: ClassMethodDecoratorContext) {
const methodName = String(context.name);
return function (this: any, ...args: any[]) {
console.log(`Calling ${methodName}`);
return target.call(this, ...args);
};
}
class Greeter {
@logged
greet(name: string) {
return `Hello, ${name}`;
}
}Legacy (experimental) decorators: still required by some frameworks
These use a different, three-argument signature (target, propertyKey, descriptor) and are what Angular, NestJS, and other established dependency-injection-heavy frameworks still require as of 2026, since they built on parameter decorators — a feature the modern standard doesn't include.
{
"compilerOptions": {
"experimentalDecorators": true, // opt IN to the legacy, pre-2023 behavior
"emitDecoratorMetadata": true
}
}Class decorators and decorator factories
A decorator factory is a function that RETURNS a decorator, letting a decorator accept its own configuration arguments — the common pattern behind decorators like @Component({...}) in frameworks that take options.
function withLabel(label: string) { // the FACTORY
return function (value: Function, context: ClassDecoratorContext) { // the actual decorator
return class extends (value as any) {
label = label;
};
};
}
@withLabel("greeter-v1")
class Greeter { }