github nestjs/swagger 12.0.0
Release 12.0.0

2 hours ago

What's Changed

@nestjs/swagger is now a native ES module, requires Nest 12, and changes how nullable schemas are spelled in the generated document.

ESM migration

The package is published as pure ESM ("type": "module", compiled with NodeNext) behind a proper exports map. The legacy root index.ts / plugin.js / plugin.ts shims are gone, and deep imports into build internals are no longer resolvable — import from the package root (@nestjs/swagger) or from @nestjs/swagger/plugin.

require(esm) — CommonJS still works

You do not need to convert your app to ESM. Thanks to Node's require(esm) support, a CommonJS app can keep doing const { SwaggerModule } = require('@nestjs/swagger'). The CLI plugin entry (@nestjs/swagger/plugin) also keeps a require condition so nest-cli.json setups load it unchanged.

This is why the package now declares "engines": { "node": "^20.19.0 || >=22.12.0" } — those are the Node versions where require(esm) is available without a flag.

Nest 12 peer dependencies

@nestjs/common and @nestjs/core peers are now ^12.0.0. @nestjs/mapped-types moves to 12.0.0 (itself ESM, with its major aligned to the Nest 12 line), so PartialType, PickType, OmitType and IntersectionType come from an ESM build too.

Standard Schema support

Schemas passed to Nest 12's route decorators (for example @Body({ schema: z.object({ ... }) })) can now be reflected into the OpenAPI document. Supply an adapter via the new standardSchemaConverter document option:

import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import type { SwaggerDocumentOptions } from '@nestjs/swagger';
import { createSchema } from 'zod-openapi';
import type { ZodType } from 'zod';

// Standard Schema exposes the producing library under `~standard.vendor`,
// which is how you narrow the raw value to a library-specific type.
function isZodSchema(schema: unknown): schema is ZodType {
  return (
    !!schema &&
    typeof schema === 'object' &&
    (schema as { '~standard'?: { vendor?: string } })['~standard']?.vendor ===
      'zod'
  );
}

const options: SwaggerDocumentOptions = {
  standardSchemaConverter: (schema, { schemaType }) => {
    if (isZodSchema(schema)) {
      const { schema: converted, components } = createSchema(schema, {
        io: schemaType,
        openapiVersion: '3.0.0'
      });
      return { schema: converted, components };
    }
  }
};

SwaggerModule.createDocument(app, config, options);

SwaggerDocumentOptions, StandardSchemaConverter and StandardSchemaConversionResult are all exported from @nestjs/swagger; createSchema comes from [zod-openapi](https://www.npmjs.com/package/zod-openapi) (for Valibot, use toJsonSchema from @valibot/to-json-schema with target: 'openapi-3.0' and check for the 'valibot' vendor instead). Neither is a dependency of this package — install whichever converter matches the schema library you use.

The callback receives the raw schema value plus whether an input or output schema is wanted, so you can narrow to library-specific types without unsafe casts, and return extra components to register. Returning undefined falls back to the DTO-derived schema, so one converter can handle several libraries and ignore the rest. Standard Schema overrides apply to bodies, queries, params, unions and enums, and take priority over the DTO-derived schema.

Breaking: nullability is spelled per document version

Nullable schemas are now normalized once on the finished document, matching the version it declares:

  • 3.1.0 and later — the nullable keyword (removed in JSON Schema 2020-12) is gone. Typed schemas become a type union (type: ['string', 'null']), enums gain a null value, and references and composite schemas become anyOf: [<schema>, { type: 'null' }]. The 3.0 type: 'object' + allOf wrapper around nullable references is unwrapped. Previously these documents carried nullable, which strict 3.1 consumers silently ignore — reading the property as non-nullable.
  • 3.0.x — nullable responses go back to the nullable keyword (with the allOf wrapper for references). Since #3897 they emitted oneOf: [<schema>, { type: 'null' }], a type: 'null' that 3.0 does not define.

The pass covers schema properties, parameters, headers, request bodies, responses, callbacks and webhooks, plus any nullable you wrote by hand. Free-form positions (example, examples, default, const, enum) and x- extensions are left alone. Snapshot tests asserting nullable: true in 3.1 documents, or oneOf in 3.0 responses, will need updating.

Closes #4063.

Breaking: lodash replaced with es-toolkit

lodash is no longer a runtime dependency — internals use es-toolkit/compat. This shrinks the install footprint and only affects you if you relied on lodash arriving transitively.

CLI plugin

  • esmCompatible is now auto-detected per file. The plugin resolves each source file's implied module format (via package.json type and the module setting) and emits ESM-compatible output for ESM projects. Setting esmCompatible explicitly in nest-cli.json still wins — the resolved value is only used when you left it unset. Fixes generated imports in ESM projects that previously got CJS-shaped output.
  • JSDoc @param tags now become descriptions. With introspectComments on, a @param tag is matched to the route parameter by name and sets the description on the generated @ApiQuery / @ApiParam. Existing explicit @ApiQuery / @ApiParam decorators are left untouched. Closes #2784.
  • A require export condition was added for the plugin entry so CJS-based CLI setups keep working. Fixes #3944.

Upgrading

For most apps the upgrade is: bump @nestjs/swagger to ^12.0.0 alongside Nest 12, make sure you are on Node 20.19+ / 22.12+, and re-check any committed OpenAPI snapshot for the nullable spelling above.

Don't miss a new swagger release

NewReleases is sending notifications on new releases.