The CLI plugin
The recommended fix for webpack: true, automatic, on every build, with no separate step to remember.
This is the same mechanism @nestjs/swagger's own CLI plugin uses to work under webpack: true. That's why @ApiProperty() isn't required on every DTO property even in a webpack build. The Nest CLI feeds a TypeScript compiler-plugin hook (compilerOptions.plugins) into both the tsc and the webpack (ts-loader) builder identically.
Register the plugin
Register nestjs-docfy in nest-cli.json:
{
"compilerOptions": {
"webpack": true,
"plugins": ["nestjs-docfy"]
}
}import { applyDocfyMetadata } from 'nestjs-docfy';
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, applyDocfyMetadata(document));| Option | Type | Default | Description |
|---|---|---|---|
metadataPath | string | docfy-metadata.json next to the running entry file | Where to read the plugin-generated metadata from. |
strict | boolean | false | Throw instead of warning when the metadata file is missing or invalid. |
Automatic detection
Forgetting to register the plugin is easy to miss until a build ships without documentation, so every generate run already checks nest-cli.json and prints a warning when webpack: true is set without the plugin. Pass --register-plugin to have it fix that for you instead of editing the file by hand:
npx nestjs-docfy generate --register-pluginThis is opt-in on purpose, since some teams deliberately stick to patch-spec instead of a compiler plugin, so generate never edits nest-cli.json unless you explicitly ask it to. It appends to any existing compilerOptions.plugins array (recognizing both string and object-form entries as already registered) and respects --dry-run.
How it works
nestjs-docfy's plugin doesn't rewrite any decorator syntax or touch the AST at all. On every compilation it re-runs the exact same static analysis generate/check/patch-spec already do (via ts-morph, against the source tree, not the bundle) and writes the resulting patch to docfy-metadata.json next to the build output.
Then, right after SwaggerModule.createDocument(), applyDocfyMetadata() reads that file and merges it in. There's no require.cache involved and no bundled-class-identity problem, because it never needs the running app to compute anything.
vs. patch-spec
If you'd rather not add a compiler plugin (for example, a build pipeline that isn't the Nest CLI, or a stricter policy about what runs during compilation), patch-spec computes the identical patch by hand against an already-built document. That was the only option before the plugin existed, and remains useful for one-off patching.
SWC builder
Nest CLI treats the SWC builder differently. It never calls before() under "builder": "swc", only a ReadonlyVisitor, and only once "typeCheck": true is set too (SWC skips type-checking on its own otherwise). This plugin exports both hooks, so turning on that setting is all it takes:
{
"compilerOptions": {
"builder": "swc",
"typeCheck": true,
"plugins": ["nestjs-docfy"]
}
}One thing worth knowing: registering the plugin this way writes a metadata.ts file next to your source root on every build. It is the same artifact @nestjs/swagger's own SWC support already produces there, empty and harmless if that plugin is not registered too.
Leave out typeCheck and the plugin goes quiet instead. The build still succeeds, but no docfy-metadata.json gets written and applyDocfyMetadata() has nothing to merge. generate catches that exact combination and warns about it.
None of this touches DocfyModule's runtime discovery. It works under SWC exactly as it does under tsc, since SWC still compiles one file per module and require.cache ends up populated the same way. If setting typeCheck is not an option, @WithDocs() plus DocfyModule.forRoot() gets you there without the plugin at all.