This error occurs when you attempt to apply a TypeScript class decorator to something other than a class declaration, such as a class expression, interface, type alias, or ambient context. Class decorators can only be used with concrete class declarations and have strict placement requirements in TypeScript.
This error reflects TypeScript's strict requirements for class decorators. A class decorator is a special TypeScript feature that allows you to augment or modify a class at compile time. However, decorators are only valid when applied directly to a class declaration—the statement that starts with the `class` keyword followed by the class name and body. The error occurs because TypeScript needs to apply the decorator to a concrete, statically-analyzable class definition. Class expressions (even named ones), interfaces, type aliases, abstract classes in declaration files, and classes in other ambient contexts don't provide the same level of compile-time certainty that decorators require. Additionally, decorators in class expressions are fundamentally incompatible with how TypeScript's decorator system works, as class expressions are evaluated at runtime as values, while decorators operate at the type level. This limitation is by design and applies to all TypeScript decorator targets: while method decorators, property decorators, and parameter decorators have their own restrictions, class decorators have the strictest placement requirement of all.
The most common cause is using a class expression instead of a class declaration. Move the class outside any expression context and define it as a standalone statement.
Bad - Class expression with decorator:
@Logger
const MyClass = class {
name = "MyClass";
};Good - Class declaration with decorator:
@Logger
class MyClass {
name = "MyClass";
}The key difference is that class MyClass { } is a declaration, while const MyClass = class { } is an expression assigned to a variable. TypeScript decorators only work with the declaration syntax.
If you're exporting a class as an expression, convert it to a declaration before applying the decorator.
Bad - Exporting class expression with decorator:
@Logger
export const MyClass = class {
getData() {
return "data";
}
};Good - Exporting class declaration with decorator:
@Logger
class MyClass {
getData() {
return "data";
}
}
export default MyClass;
// OR
export { MyClass };You can still export the decorated class normally; just make sure the decorator is applied to the declaration, not the expression.
Ensure that decorators are enabled in your TypeScript configuration. Even though modern TypeScript treats decorators as default syntax for declarations, you may need to explicitly enable experimental support depending on your setup.
tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}The experimentalDecorators option enables decorator syntax, and emitDecoratorMetadata allows libraries like NestJS and TypeORM to use reflection metadata. After changing tsconfig.json, restart your TypeScript server or IDE.
If you're defining a class inside a function or trying to apply decorators to classes in conditional contexts, move the class to the module level.
Bad - Class defined inside function with decorator:
function createClass() {
@Logger // ❌ Decorator in function context
class MyClass {
value = 42;
}
return MyClass;
}Good - Class at module level:
@Logger
class MyClass {
value = 42;
}
function createClass() {
return MyClass;
}If you need dynamic class creation, consider using factory functions that return instances of pre-declared decorated classes instead of defining new classes conditionally.
Decorators cannot be applied to interfaces or type aliases because they don't exist at runtime. Use concrete class declarations instead.
Bad - Decorator on interface:
@Logger // ❌ Interfaces don't support decorators
interface IMyClass {
getData(): string;
}Bad - Decorator on type:
@Logger // ❌ Types don't support decorators
type MyType = {
name: string;
};Good - Use a class instead:
@Logger
class MyClass {
name: string;
getData(): string {
return this.name;
}
}If you only need the interface for type checking, use both: a decorated class and an interface that extends it or documents its contract.
If you're trying to use decorators with mixins (returning class expressions from functions), refactor to apply decorators to the concrete class instead.
Bad - Decorator on mixin pattern:
function applyTimestamp<T extends { new(...args: any[]): {} }>(Base: T) {
@Logger // ❌ Cannot decorate the return value
return class extends Base {
timestamp = new Date();
};
}Good - Declare and decorate the final class:
// Create the class separately
class TimestampedClass extends BaseClass {
timestamp = new Date();
}
// Then decorate it
@Logger
class MyClass extends TimestampedClass {
name = "MyClass";
}Alternatively, use factory functions that return instances of properly decorated classes.
Decorators cannot be used in declare statements or .d.ts files. If you're working in a declaration file, move the decorated class to a regular .ts implementation file.
Bad - Decorator in .d.ts file:
// types.d.ts
@Logger // ❌ Cannot use decorators in declaration files
declare class MyClass {
getData(): string;
}Good - Implementation in .ts file:
// myclass.ts
@Logger
class MyClass {
getData(): string {
return "data";
}
}
export { MyClass };Declaration files are for type information only and don't execute decorators. Keep decorated classes in implementation files and export their types as needed.
TypeScript decorators operate at the type system level and require static analyzability. This is why class expressions—even when assigned to constants—aren't supported. The decorators specification and TypeScript's implementation both assume that decorators are applied to declarations that exist in the abstract syntax tree as distinct statements, not as expressions.
The strict class declaration requirement also applies to abstract classes defined in ambient contexts (declare abstract class). If you need to decorate an abstract class, it must be a real implementation, not a declaration file definition.
When working with frameworks like NestJS, Angular, or TypeORM that heavily rely on decorators, remember that all your service classes, controller classes, and entity classes must be declared as class declarations, not expressions. This affects patterns like exporting anonymous classes or using class expressions as mixins.
If you're migrating from other languages with more flexible decorator systems, note that TypeScript's restriction is intentional—it enables better tooling, type safety, and predictable behavior. Modern TypeScript (5.0+) has relaxed some experimental restrictions but still maintains the class declaration requirement for class decorators. Always apply decorators immediately before the class keyword in the source code.
For testing scenarios, if you need to create test doubles of decorated classes, create proper subclasses that extend the decorated class rather than trying to apply decorators dynamically at runtime.
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