TS1206Syntax Error
Since TS 1.5

Fix TS1206: Decorators Are Not Valid Here

Learn why TypeScript throws TS1206 when a decorator sits on a function, a constructor, a variable or a parameter, and how to fix each case.

error TS1206: Decorators are not valid here

What This Error Means

TS1206 means you wrote a decorator in a place where the language does not allow one. TypeScript's parser is deliberately permissive about @expression — it reads the decorator first and asks questions later — so the complaint comes from the grammar checker that runs right after parsing, once it can see what you attached the decorator to.

The list of legal targets is short. A decorator may sit on a class declaration and on class members: methods, getters and setters, and fields. Everything else is out: function declarations, variable statements, the constructor keyword, and — this is the one that catches whole teams — parameters, unless experimentalDecorators is enabled. Since TypeScript 5.0 the default decorator implementation is the ECMAScript (TC39) proposal, which has no parameter decorators at all, so a dependency-injection codebase that loses its experimentalDecorators flag lights up with TS1206 on every @Inject().

// The general shape of the error:
@logged
export function fetchOrders() {}
// ~~~~~~~ Error: Decorators are not valid here.

Because this is a grammar check and not a type check, strict and friends make no difference. The only compiler option that changes the outcome is experimentalDecorators, and it changes it in both directions: it allows parameter decorators and it forbids decorators on class expressions, which the standard mode permits.

Common Causes

1. A Parameter Decorator Without experimentalDecorators

This is by far the most common report, and it almost always arrives with a TypeScript upgrade rather than with new code. NestJS, Angular, TypeORM and InversifyJS all inject dependencies through parameter decorators, which only exist in the legacy proposal.

// ❌ Broken — TypeScript 5.x with the default (standard) decorators
export class OrdersController {
  constructor(@Inject(ORDERS) private readonly orders: OrdersService) {}
  //          ~~~~~~~~~~~~~~~ Error: Decorators are not valid here.
}
// ✅ Fixed — same code, with "experimentalDecorators": true in tsconfig.json
export class OrdersController {
  constructor(@Inject(ORDERS) private readonly orders: OrdersService) {}
}

The fix is a config change, not a code change:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

emitDecoratorMetadata belongs with it for any framework that resolves dependencies from types rather than from explicit tokens. If the error only shows up in one place — your editor but not the build, or Vite but not tsc — the flag is set in one tsconfig.json and missing from another. An extends chain that was rewritten, a separate tsconfig.spec.json, or an esbuild-based dev server reading its own tsconfigRaw are the usual suspects.

2. A Decorator on a Function Declaration

Decorators are a class feature in both proposals. There is no wording in either specification that attaches one to a standalone function, so this fires no matter which mode you are in.

// ❌ Broken
@measure
export function fetchOrders(customerId: string) {
  return fetch(`/api/customers/${customerId}/orders`)
}
// ~~~~~~~~ Error: Decorators are not valid here.
// ✅ Fixed — a higher-order function does the same job
function measure<T extends (...args: never[]) => unknown>(fn: T): T {
  return function (this: unknown, ...args: never[]) {
    const started = performance.now()
    try {
      return fn.apply(this, args)
    } finally {
      console.log(`${fn.name} took ${performance.now() - started}ms`)
    }
  } as T
}
 
export const fetchOrders = measure(function fetchOrders(customerId: string) {
  return fetch(`/api/customers/${customerId}/orders`)
})

Wrapping is what a decorator does under the hood anyway — it receives the target and returns a replacement. Writing the call yourself costs one line and works in every TypeScript version. If the behaviour really belongs to a group of related functions, the other option is to make them methods on a class, where @measure becomes legal.

3. A Decorator on the constructor

A constructor looks like a method, so decorating it looks reasonable. It is not a method: it has no independent name, and a decorator that replaced it would have nothing to attach the replacement to. Both decorator modes reject it.

// ❌ Broken
export class ReportBuilder {
  @track
  constructor(private readonly title: string) {}
  // ~~~~~~ Error: Decorators are not valid here.
}
// ✅ Fixed — decorate the class, which wraps construction
@track
export class ReportBuilder {
  constructor(private readonly title: string) {}
}

A class decorator receives the constructor as its argument and can return a subclass, so anything you wanted to do "around the constructor" is available there — logging instantiation, registering the class, replacing it with a proxy. Decorating individual parameters of the constructor is a different question, and that one depends on experimentalDecorators — see cause 1.

4. A Decorator on a Variable Statement

A decorator in front of const, let or var reads naturally and is rejected by both modes. This usually shows up after someone moves a decorated class member out to module scope, or copies a decorator from a framework example into the wrong place.

// ❌ Broken
@logged
export const defaultTimeoutMs = 5000
// ~~~~~~~ Error: Decorators are not valid here.
// ✅ Fixed — call the helper instead of decorating the declaration
function logged<T>(label: string, value: T): T {
  console.log(`${label} = ${String(value)}`)
  return value
}
 
export const defaultTimeoutMs = logged("defaultTimeoutMs", 5000)

One nearby case goes the other way. Decorating a class expression — const PricingService = @track class {} — is legal under the standard decorators of TypeScript 5.0 and later, and reports TS1206 only when experimentalDecorators is on. If you have that line and the flag, promote the class expression to a class declaration.

How to Fix It

  1. Decide which decorator mode the project needs, once. If any dependency uses parameter decorators — NestJS, Angular, TypeORM, InversifyJS, routing-controllers — set "experimentalDecorators": true (usually with "emitDecoratorMetadata": true) and stop there. If nothing does, keep the TypeScript 5 default and remove the parameter decorators instead. Mixing the two mental models is what produces confusing errors later.

  2. Check every tsconfig that reads the file. The flag has to be visible from the config that is actually in effect: the root tsconfig.json, any tsconfig.app.json / tsconfig.spec.json, and whatever your bundler resolves. Run npx tsc --showConfig in the directory in question and look for experimentalDecorators in the output — that is the resolved value, extends chain included. For an esbuild-backed dev server (Vite, tsx), set it in the tsconfig the tool reads or pass it through esbuild.tsconfigRaw.

  3. Move the decorator to a legal target. Class declaration, method, getter, setter, field — those four always work. A constructor becomes its class; a function becomes a method; a parameter that cannot use experimentalDecorators becomes an explicit argument in the constructor call.

  4. Replace the decorator with a plain function call where the target cannot move. const handler = withRetry(fetchOrders) does what @withRetry would have done and needs no compiler flags. This is the right answer for module-scope functions and constants, and it stays valid when the decorator proposal changes again.

  5. Do not reach for // @ts-ignore or // @ts-expect-error. TS1206 is a grammar error, so suppression comments do not remove it, and even where a suppression appears to work the decorator is simply not applied — you get silently missing dependency injection at runtime rather than a compile error. Fix the target or the flag.

  6. Pin the decision in the repo. Record the chosen mode in the base tsconfig.json that every other config extends, and keep the flag out of individual project configs so it cannot drift. A single upgrade of TypeScript then cannot turn a working DI setup into a wall of TS1206.

FAQ

What causes TypeScript error TS1206?

TS1206 is a grammar error, raised after parsing but before type checking. TypeScript reads @expression wherever it appears and only afterwards checks whether the decorated thing is a legal target.

Legal targets are class declarations and class members — methods, getters, setters and fields. Function declarations, variable statements and the constructor keyword are never legal. Parameters are legal only under experimentalDecorators, and class expressions are legal only without it.

Why does @Inject() give TS1206 after upgrading to TypeScript 5?

Because TypeScript 5.0 switched the default to the standard ECMAScript decorators, and that proposal has no parameter decorators. Before 5.0 you needed experimentalDecorators to use decorators at all, so most projects had it on and never thought about it; after 5.0 decorators work without the flag, which makes it easy to drop while the parameter decorators quietly stop being valid.

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

NestJS, Angular and TypeORM all need the legacy semantics. If the CLI-generated config sets the flag but your build still fails, some other config is winning — confirm with npx tsc --showConfig.

Can I put a decorator on a function in TypeScript?

No. Neither proposal allows it, so @measure function fetchOrders() {} reports TS1206 in every TypeScript version and in both decorator modes. The same is true of arrow functions assigned to a const, because that is a variable statement.

Use a higher-order function: export const fetchOrders = measure(function fetchOrders() { /* ... */ }). A decorator is just a function that receives the target and returns a replacement, so calling it yourself gives you exactly the same behaviour with no compiler configuration at all. If several related functions need the same wrapper, making them methods on a class also works, since methods are a valid decorator target.

Practice This

Browse all TypeScript practice challenges to keep sharpening your type-level skills.

Share this reference

Become a TypeScript Pro

Track your progress through 100+ hands-on challenges. Free, sign in with GitHub.

Or start solving right away: explore all TypeScript challenges