nestjs/nest · critical · InvalidClassModuleException
"${metatype.name}" is decorated with @Controller() and canno
Error message
"${metatype.name}" is decorated with @Controller() and cannot appear in the "imports" array of a module. Please move "${metatype.name}" to the "controllers" array of the importing module instead.
Scope [${scope}]
What it means
The controller counterpart of the injectable-in-imports guard: a class bearing the @Controller() watermark in an `imports` array triggers InvalidClassModuleException — '"X" is decorated with @Controller() and cannot appear in the imports array of a module. Please move X to the controllers array of the importing module instead.' Controllers are never importable DI units; only modules are.
Source
Thrown at packages/core/scanner.ts:206
| {
moduleRef: Module;
inserted: boolean;
}
| undefined
> {
const moduleToAdd = this.isForwardReference(moduleDefinition)
? moduleDefinition.forwardRef()
: moduleDefinition;
if (this.isInjectable(moduleToAdd)) {
throw new InvalidClassModuleException(
moduleDefinition,
scope,
'provider',
);
}
if (this.isController(moduleToAdd)) {
throw new InvalidClassModuleException(
moduleDefinition,
scope,
'controller',
);
}
if (this.isExceptionFilter(moduleToAdd)) {
throw new InvalidClassModuleException(moduleDefinition, scope, 'filter');
}
return this.container.addModule(moduleToAdd, scope);
}
public async scanModulesForDependencies(
modules: Map<string, Module> = this.container.getModules(),
) {
for (const [token, { metatype }] of modules) {
await this.reflectImports(metatype, token, metatype.name);
this.reflectProviders(metatype, token);View on GitHub (pinned to dd75d7bd8c)
Solutions
- Move the class from `imports` to the `controllers` array of the same module.
- To use a controller in another module, re-declare it in that module's `controllers` (controllers are scoped to their module) or move it to a shared module that both import.
- If shared behavior (not routes) is the goal, extract it into an @Injectable() service and export that instead.
Example fix
// before
@Module({
imports: [OrdersController], // wrong array
})
export class OrdersModule {}
// after
@Module({
controllers: [OrdersController],
providers: [OrdersService],
})
export class OrdersModule {} Defensive patterns
Strategy: type-guard
Validate before calling
// Wiring test: nothing in imports arrays may be @Controller()
import { CONTROLLER_WATERMARK } from '@nestjs/common/constants';
import 'reflect-metadata';
function assertNoControllersInImports(modules: Function[]) {
for (const m of modules) {
for (const imported of Reflect.getMetadata('imports', m) ?? []) {
const target = imported && 'forwardRef' in (imported as any) ? (imported as any).forwardRef() : imported;
if (typeof target === 'function' && Reflect.getMetadata(CONTROLLER_WATERMARK, target)) {
throw new Error(`${m.name}: @Controller() ${target.name} belongs in controllers, not imports`);
}
}
}
} Type guard
const isControllerClass = (v: any): boolean =>
typeof v === 'function' && !!Reflect.getMetadata('__controller__', v); Prevention
- Register controllers only in the controllers array of the module whose scope they serve.
- To share controller behavior across modules, extract services into a shared module instead of importing controllers.
- Add the watermark scan to wiring tests so array mix-ups fail before bootstrap.
When it happens
Trigger: Listing a controller class in `imports` instead of `controllers`; trying to 'share' a controller between modules by importing it (the correct pattern is re-declaring it in each module's controllers or restructuring); paste errors between the arrays.
Common situations: Wanting one controller to serve multiple modules; teams migrating from frameworks where route handlers are registered globally; array mix-ups during module splits.
Related errors
- An invalid controller has been detected. "${className}" does
- Nest cannot export a provider/module that is not a part of t
- Nest cannot create the ${parentModule.name} instance. The mo
- Nest cannot create the ${parentModule.name} instance. Receiv
- "${metatype.name}" is decorated with @Injectable() and canno
AI-assisted analysis of nestjs/nest@dd75d7bd8c (2026-08-21).
Data as JSON: /api/errors/85f55c3dd1eb86b9.
Report an issue: GitHub.