How to fix Decorator 'X' must be used on a class method in TypeScript

TypeScriptINTERMEDIATEMEDIUMUpdated Dec 8, 2025· 111 views

This TypeScript error occurs when a method-specific decorator is applied to the wrong target, such as a property, class declaration, or parameter. The fix involves moving the decorator to a proper class method or using the correct decorator type for your target.

Quick fix

Fixes Decorator 'X' must be used on a class method

// Method decorator signature
function MethodDecorator(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor  // ← Only methods have descriptors
): PropertyDescriptor | void;

// Property decorator signature (no descriptor)
function PropertyDecorator(
  target: any,
  propertyKey: string
): void;
  1. Identify the decorator's intended target
  2. Move decorator to proper class method
  3. Use correct decorator type for target
  4. Fix class expression decorator usage
typescript — error
// Method decorator signature
function MethodDecorator(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor // ← Only methods have descriptors
): PropertyDescriptor | void;
// Property decorator signature (no descriptor)
function PropertyDecorator(
target: any,
propertyKey: string
): void;
Decorator 'X' must be used on a class method

What this error means

This error appears when you attempt to apply a decorator that was designed specifically for class methods to a different kind of class member. TypeScript decorators have strict target restrictions - a method decorator can only be applied to methods, property decorators to properties, parameter decorators to parameters, and so on. Method decorators are special because they receive a property descriptor parameter that allows them to wrap or modify the method's behavior. When applied to a property, parameter, or class declaration, TypeScript cannot provide this descriptor, resulting in a compilation error. Common scenarios include: 1. **Applying method decorators to properties**: Using decorators like @debounce or @memoize on class fields instead of methods 2. **Confusing class vs method decorators**: Putting a method decorator on the class declaration itself 3. **Using decorators on class expressions**: Decorators aren't allowed on classes assigned to variables 4. **Framework-specific restrictions**: Libraries like Angular, NestJS, or TypeORM have decorators with specific target requirements

Typical symptoms

  • Compilation fails with 'Decorator must be used on a class method' or similar message
  • IDE shows error highlighting on decorator usage
  • Decorator that works on methods fails when moved to a property
  • Framework decorators reject non-method class members
  • Error occurs when refactoring methods to properties with arrow functions
  • Decorators work on class declarations but fail on class expressions

Common causes

  1. Method decorator applied to a class property instead of a method
  2. Decorator placed on class declaration when it requires a method target
  3. Using method-specific decorator on a parameter or accessor
  4. Refactoring method to arrow function property without changing decorator
  5. Framework decorator used outside its intended target (e.g., @Get() on property in NestJS)
  6. Attempting to use decorators on class expressions assigned to variables
  7. Mixing up decorator types from validation or routing libraries
  8. Custom decorator implementation that validates target type incorrectly

How to fix "Decorator 'X' must be used on a class method"

1Identify the decorator's intended target

First, check what type of class member the decorator is designed for. Look at the decorator's type signature or documentation:

typescript
// Method decorator signature
function MethodDecorator(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor  // ← Only methods have descriptors
): PropertyDescriptor | void;

// Property decorator signature (no descriptor)
function PropertyDecorator(
  target: any,
  propertyKey: string
): void;

Check your code to see what you're applying it to:

typescript
class Example {
  // WRONG - @LogMethod expects a method, not a property
  @LogMethod
  name: string = "test";

  // CORRECT - @LogMethod decorates a method
  @LogMethod
  getName(): string {
    return this.name;
  }
}

If you're using a framework decorator (Angular, NestJS, etc.), consult the framework documentation for proper usage.

2Move decorator to proper class method

If you have a method decorator on a property, convert the property to a method:

typescript
// BEFORE - Error: @Debounce must be used on a class method
class SearchComponent {
  @Debounce(500)
  query: string = "";
}

// AFTER - Fixed by converting to method
class SearchComponent {
  private _query: string = "";

  @Debounce(500)
  setQuery(value: string): void {
    this._query = value;
  }

  getQuery(): string {
    return this._query;
  }
}

For arrow function properties, convert to regular methods:

typescript
// BEFORE - Arrow function property
class Handler {
  @LogExecution
  process = (data: any) => {
    // Error: decorators can't be used on arrow function properties
  };
}

// AFTER - Regular method
class Handler {
  @LogExecution
  process(data: any) {
    return data;
  }
}
3Use correct decorator type for target

If the decorator is on the wrong type of class member, use or create the appropriate decorator:

typescript
// For properties, use property decorators
function PropertyLogger(target: any, propertyKey: string) {
  console.log(`Property: ${propertyKey}`);
}

// For methods, use method decorators
function MethodLogger(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${propertyKey}`);
    return originalMethod.apply(this, args);
  };
}

class Example {
  @PropertyLogger  // ✓ Property decorator on property
  name: string = "test";

  @MethodLogger    // ✓ Method decorator on method
  greet(): void {
    console.log(`Hello ${this.name}`);
  }
}

For framework decorators, use the correct variant:

typescript
// NestJS example
import { Controller, Get, Post } from '@nestjs/common';

@Controller('users')  // ✓ Class decorator on class
class UserController {
  @Get()              // ✓ Method decorator on method
  findAll() {
    return [];
  }

  // WRONG - @Get() on property
  // @Get()
  // users = [];
}
4Fix class expression decorator usage

Decorators cannot be used on class expressions assigned to variables. Convert to class declarations:

typescript
// WRONG - Decorators not allowed on class expressions
const MyClass = @Injectable class {
  @LogMethod
  doSomething() {}
};

// CORRECT - Use class declaration
@Injectable
class MyClass {
  @LogMethod
  doSomething() {}
}

// If you need to export as const, do it separately
export const MyServiceInstance = MyClass;

For factory patterns, use decorator on the class before returning:

typescript
// BEFORE - Won't work
function createService() {
  return @Service class ServiceImpl {
    @Cached
    getData() {}
  };
}

// AFTER - Declare class first
@Service
class ServiceImpl {
  @Cached
  getData() {}
}

function createService() {
  return ServiceImpl;
}
5Enable experimentalDecorators in tsconfig.json

Ensure decorator support is enabled in your TypeScript configuration:

json
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,  // If using reflection
    "target": "ES2015"
  }
}

For TypeScript 5.0+ with stage 3 decorators (different syntax):

json
{
  "compilerOptions": {
    // No experimentalDecorators flag needed for stage 3 decorators
    "target": "ES2022"
  }
}

Note: Stage 3 decorators (TypeScript 5.0+) have different behavior and syntax than experimental decorators. If migrating, check decorator library compatibility.

After updating tsconfig.json, restart your TypeScript server:
- VS Code: Cmd/Ctrl + Shift + P → "TypeScript: Restart TS Server"
- Or restart your IDE

6Verify decorator implementation for custom decorators

If you're creating custom decorators, ensure the implementation matches the target type:

typescript
// Method decorator - includes descriptor parameter
export function LogMethod(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
): PropertyDescriptor {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    console.log(`[${propertyKey}] called with:`, args);
    const result = originalMethod.apply(this, args);
    console.log(`[${propertyKey}] returned:`, result);
    return result;
  };

  return descriptor;
}

// Property decorator - NO descriptor parameter
export function DefaultValue(value: any) {
  return function (target: any, propertyKey: string) {
    let currentValue = value;

    Object.defineProperty(target, propertyKey, {
      get() {
        return currentValue;
      },
      set(newValue) {
        currentValue = newValue;
      },
      enumerable: true,
      configurable: true,
    });
  };
}

// Usage
class Example {
  @DefaultValue("Unknown")  // ✓ Property decorator
  name: string;

  @LogMethod                // ✓ Method decorator
  getName(): string {
    return this.name;
  }
}

Test your decorator on the correct target before deploying.

Advanced notes

### Decorator Target Validation

You can enforce target restrictions in custom decorators by checking the descriptor parameter:

typescript
function MethodOnly(
  target: any,
  propertyKey: string,
  descriptor?: PropertyDescriptor
) {
  if (!descriptor) {
    throw new Error(
      `@MethodOnly decorator can only be applied to methods. \
      Found on property: ${propertyKey}`
    );
  }

  // Decorator logic here
  return descriptor;
}

### Decorator Evaluation Order

When multiple decorators are applied, they evaluate in a specific order:

1. Expressions evaluate top-to-bottom
2. Results are called as functions bottom-to-top

typescript
function First() {
  console.log("First(): evaluated");
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("First(): called");
  };
}

function Second() {
  console.log("Second(): evaluated");
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("Second(): called");
  };
}

class Example {
  @First()
  @Second()
  method() {}
}

// Output:
// First(): evaluated
// Second(): evaluated
// Second(): called
// First(): called

### Stage 3 Decorators vs Experimental Decorators

TypeScript 5.0 introduced support for the ECMAScript stage 3 decorator proposal, which differs from experimental decorators:

Experimental Decorators (legacy):
- Require experimentalDecorators: true
- Most existing libraries use this (Angular, NestJS, TypeORM)
- Decorators are functions that receive target, key, descriptor

Stage 3 Decorators (modern):
- Default in TypeScript 5.0+ without flags
- Different signature and behavior
- Use decorator context object instead of separate parameters

typescript
// Experimental (old)
function LogMethod(target: any, key: string, descriptor: PropertyDescriptor) {
  // ...
}

// Stage 3 (new)
function LogMethod(target: Function, context: ClassMethodDecoratorContext) {
  // context.kind === "method"
  // context.name is the method name
  // context.access provides getter/setter
}

Compatibility warning: Stage 3 and experimental decorators are not compatible. Pick one system for your project.

### Accessor Decorators

Accessor decorators are a special case - they can only be applied to getters/setters, not both:

typescript
function Configurable(value: boolean) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    descriptor.configurable = value;
  };
}

class Example {
  private _value: number = 0;

  @Configurable(false)  // ✓ OK on getter
  get value(): number {
    return this._value;
  }

  // WRONG - Can't decorate both get and set
  // @Configurable(false)
  set value(val: number) {
    this._value = val;
  }
}

Apply the decorator to the first accessor (get or set) only.

### Framework-Specific Decorator Restrictions

Angular:
- @Component, @Injectable, @NgModule → Class decorators only
- @Input, @Output, @ViewChild → Property decorators only
- @HostListener → Method decorators only

NestJS:
- @Controller, @Injectable → Class decorators
- @Get, @Post, @Put → Method decorators
- @Body, @Param, @Query → Parameter decorators

TypeORM:
- @Entity, @Index → Class decorators
- @Column, @PrimaryKey → Property decorators
- @BeforeInsert, @AfterLoad → Method decorators

Always check your framework's documentation for proper decorator placement.

### Debugging Decorator Issues

Use tsc --listEmittedFiles to see generated JavaScript and understand how decorators compile:

bash
npx tsc --listEmittedFiles

For runtime debugging, add console logs in decorators:

typescript
function Debug(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  console.log("Target:", target);
  console.log("Property:", propertyKey);
  console.log("Descriptor:", descriptor);
  return descriptor;
}

This shows exactly what TypeScript passes to your decorator at compile time.

Frequently asked questions

What does "Decorator 'X' must be used on a class method" mean?
This error appears when you attempt to apply a decorator that was designed specifically for class methods to a different kind of class member. TypeScript decorators have strict target restrictions - a method decorator can only be applied to methods, property decorators to properties, parameter decorators to parameters, and so on. Method decorators are special because they receive a property descriptor parameter that allows them to wrap or modify the method's behavior.
How do I fix "Decorator 'X' must be used on a class method"?
This TypeScript error occurs when a method-specific decorator is applied to the wrong target, such as a property, class declaration, or parameter. The fix involves moving the decorator to a proper class method or using the correct decorator type for your target.
What causes "Decorator 'X' must be used on a class method"?
Method decorator applied to a class property instead of a method Decorator placed on class declaration when it requires a method target Using method-specific decorator on a parameter or accessor
Is the "Decorator 'X' must be used on a class method" error serious?
It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading