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.
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
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.
### 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.
Function expression requires a return type
Function expression requires a return type
Value of type 'string | undefined' is not iterable
How to fix "Value is not iterable" in TypeScript
Type 'undefined' is not assignable to type 'string'
How to fix "Type undefined is not assignable to type string" in TypeScript
Type narrowing from typeof check produces 'never'
How to fix "Type narrowing produces never" in TypeScript
Type parameter 'T' has conflicting constraints
How to fix "Type parameter has conflicting constraints" in TypeScript