How to fix Decorator 'X' must be used on a class method in TypeScript
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;- Identify the decorator's intended target
- Move decorator to proper class method
- Use correct decorator type for target
- Fix class expression decorator usage
// Method decorator signaturefunction 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
- 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
- Refactoring method to arrow function property without changing decorator
- Framework decorator used outside its intended target (e.g., @Get() on property in NestJS)
- Attempting to use decorators on class expressions assigned to variables
- Mixing up decorator types from validation or routing libraries
- Custom decorator implementation that validates target type incorrectly
How to fix "Decorator 'X' must be used on a class method"
First, check what type of class member the decorator is designed for. Look at the decorator's type signature or documentation:
// 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:
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.
If you have a method decorator on a property, convert the property to a method:
// 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:
// 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;
}
}If the decorator is on the wrong type of class member, use or create the appropriate decorator:
// 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:
// 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 = [];
}Decorators cannot be used on class expressions assigned to variables. Convert to class declarations:
// 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:
// 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;
}Ensure decorator support is enabled in your TypeScript configuration:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true, // If using reflection
"target": "ES2015"
}
}For TypeScript 5.0+ with stage 3 decorators (different syntax):
{
"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
If you're creating custom decorators, ensure the implementation matches the target type:
// 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:
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
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
// 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:
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:
npx tsc --listEmittedFilesFor runtime debugging, add console logs in decorators:
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
- 📚TypeScript Official Documentation
- 📖TypeScript Handbook - Decorators(Official Docs)
- 📖TypeScript 5.0 - Stage 3 Decorators(Official Docs)
- 📖TypeScript TSConfig - experimentalDecorators(Official Docs)
- 🐙
- 🐙