Is there an existing issue for this?
Current behavior
PR #3725 made @ApiTags() accept an ApiTagOptions object form so it can co-exist with strings:
@ApiTags({ name: 'Cats', parent: 'Animals', kind: 'navigation' })
class CatsController {}
However, parent and kind are silently discarded by the decorator implementation — only name is preserved. The PR's own test suite codifies this behavior at test/decorators/api-use-tags.decorator.spec.ts:
@ApiTags({ name: 'Cats', parent: 'Animals' })
objectWithParent() {}
// asserts:
expect(tags).toEqual(['Cats']);
The architecture supports this drop today: SwaggerScanner.scanApplication() does not aggregate tag definitions into document.tags — the root-level tags array is populated only by DocumentBuilder.addTag(). The decorator-level metadata has nowhere to land.
Net effect for consumers: passing @ApiTags({ name: 'Pets', parent: 'Animals' }) is exactly equivalent to @ApiTags('Pets'), but the type signature suggests otherwise.
Expected behavior
One of:
-
Propagate — @ApiTags(ApiTagOptions) aggregates definitions into document.tags, deduping by name with entries declared via DocumentBuilder.addTag(). This requires:
- Decorator persists full options (in addition to/instead of just names).
- A new global tag-definitions explorer.
- Scanner aggregates and returns them in
document.tags.
SwaggerModule.createDocument merges scanner-collected tags with config tags.
-
Reject / warn — emit a runtime warning (or compile-time error via type narrowing) when hierarchy fields are set on @ApiTags, redirecting users to DocumentBuilder.addTag(). Less surprising than silent drop.
Option (1) matches what the type signature already promises and is what most users would expect. Option (2) keeps the current architecture intact.
Happy to open a PR once a direction is decided. Related: #3921 (adds summary field at the builder level — same OpenAPI 3.2 work, deliberately scoped narrower because of this open question).
Package version
@nestjs/swagger >= 11.4.0
What is the motivation / use case for changing the behavior?
OpenAPI 3.2 hierarchical tags are useful for organizing large APIs. Today users have to declare every tag's parent/kind via DocumentBuilder.addTag() in main.ts, even though @ApiTags() looks like it accepts the same options.
Is there an existing issue for this?
Current behavior
PR #3725 made
@ApiTags()accept anApiTagOptionsobject form so it can co-exist with strings:However,
parentandkindare silently discarded by the decorator implementation — onlynameis preserved. The PR's own test suite codifies this behavior attest/decorators/api-use-tags.decorator.spec.ts:The architecture supports this drop today:
SwaggerScanner.scanApplication()does not aggregate tag definitions intodocument.tags— the root-leveltagsarray is populated only byDocumentBuilder.addTag(). The decorator-level metadata has nowhere to land.Net effect for consumers: passing
@ApiTags({ name: 'Pets', parent: 'Animals' })is exactly equivalent to@ApiTags('Pets'), but the type signature suggests otherwise.Expected behavior
One of:
Propagate —
@ApiTags(ApiTagOptions)aggregates definitions intodocument.tags, deduping bynamewith entries declared viaDocumentBuilder.addTag(). This requires:document.tags.SwaggerModule.createDocumentmerges scanner-collected tags with config tags.Reject / warn — emit a runtime warning (or compile-time error via type narrowing) when hierarchy fields are set on
@ApiTags, redirecting users toDocumentBuilder.addTag(). Less surprising than silent drop.Option (1) matches what the type signature already promises and is what most users would expect. Option (2) keeps the current architecture intact.
Happy to open a PR once a direction is decided. Related: #3921 (adds
summaryfield at the builder level — same OpenAPI 3.2 work, deliberately scoped narrower because of this open question).Package version
@nestjs/swagger >= 11.4.0
What is the motivation / use case for changing the behavior?
OpenAPI 3.2 hierarchical tags are useful for organizing large APIs. Today users have to declare every tag's
parent/kindviaDocumentBuilder.addTag()inmain.ts, even though@ApiTags()looks like it accepts the same options.