This document tracks the Zod 4 surface that next-openapi-gen currently validates in code and fixtures.
The generator still relies primarily on static AST analysis, but it now uses a selective runtime-assisted Zod 4 export path for a small set of features where input/output-aware JSON Schema adds useful fidelity without replacing the existing AST converter.
| Zod 4 construct | Expected emitted shape | OpenAPI targets | Regression coverage | Notes |
|---|---|---|---|---|
z.email() |
type: "string", format: "email" |
3.0, 3.1, 3.2 |
tests/unit/schema/zod/zod-converter.test.ts, tests/integration/generator/zod4-support.test.ts |
Same parity as z.string().email() |
z.url() |
type: "string", format: "uri" |
3.0, 3.1, 3.2 |
tests/unit/schema/zod/zod-converter.test.ts, tests/integration/generator/zod4-support.test.ts |
Same parity as z.string().url() |
z.uuid() |
type: "string", format: "uuid" |
3.0, 3.1, 3.2 |
tests/unit/schema/zod/zod-converter.test.ts, tests/integration/generator/zod4-support.test.ts |
Closes issue #92 |
z.iso.datetime() |
type: "string", format: "date-time" |
3.0, 3.1, 3.2 |
tests/unit/schema/zod/zod-converter.test.ts, tests/integration/generator/zod4-support.test.ts |
Nested namespace helper support |
z.guid() / z.ipv4() / z.ipv6() / z.iso.duration() |
String formats are preserved in emitted schemas | 3.0, 3.1, 3.2 |
tests/unit/schema/zod/node-helpers.test.ts, tests/unit/schema/zod/zod-converter.test.ts |
Added to the AST converter parity path |
nullable() / nullish() on base schemas and named schema references |
nullable: true in 3.0; type: [..., "null"] in 3.1+ for primitive types. For named schema references (producing a $ref), emits anyOf: [{ $ref }, { type: "null" }]; the version processor downgrades to { allOf: [{ $ref }], nullable: true } for 3.0. |
3.0, 3.1, 3.2 |
tests/integration/generator/zod4-support.test.ts, tests/integration/validation/openapi-validation.test.ts, tests/integration/regressions/zod-nullability.test.ts |
Closes issue #142; version finalization rewrites nullable semantics |
pipe() into a stronger schema |
Preserves strongest representable base schema | 3.0, 3.1, 3.2 |
tests/unit/schema/zod/zod-converter.test.ts |
Used for patterns like z.string().pipe(z.email()) |
Runtime-assisted coerce / pipe variants |
Request-side schemas can keep input shapes while responses keep output constraints when the runtime export can prove the difference | 3.1, 3.2 |
tests/unit/schema/zod/zod-converter.test.ts |
Falls back to AST behavior when runtime export is unavailable |
transform() / refine() / superRefine() / brand() |
Preserve the underlying JSON-schema-compatible base shape | 3.0, 3.1, 3.2 |
tests/unit/schema/zod/zod-converter.test.ts, tests/integration/generator/zod4-support.test.ts |
Runtime-only semantics are not serialized |
.describe("text") / .meta({ description }) — OpenAPI description |
Maps to description in emitted schema. .describe() and .meta({ description }) are equivalent. @deprecated prefix sets deprecated: true. |
3.0, 3.1, 3.2 |
tests/unit/schema/zod/features/modifiers.test.ts |
Idiomatic Zod-native alternative to @description JSDoc |
.meta({...}) — OpenAPI annotations (Zod v4) |
Copies all representable keys into emitted schema: description, examples, example, deprecated, title, custom x-* extensions. The id field is treated specially: it overrides the generated component name instead of being emitted as a schema property (see Component Naming). Example: z.number().int().positive().meta({ description: "PIM ID", examples: [42, 1337] }) → { type: "integer", exclusiveMinimum: 0, description: "PIM ID", examples: [42, 1337] } |
3.0, 3.1, 3.2 |
tests/unit/schema/zod/features/modifiers.test.ts, tests/unit/schema/zod/zod-converter.test.ts, tests/unit/schema/zod/runtime-exporter.test.ts |
Runtime-assisted path via z.toJSONSchema() when .meta() is outermost call; AST path for mid-chain usage |
z.literal() with numeric values |
Integer values emit type: "integer"; float values emit type: "number". A z.union([z.literal(1), z.literal(2)]) composed entirely of integer literals collapses to { type: "integer", enum: [...] }. Mixed integer/float unions fall through to separate anyOf items. |
3.0, 3.1, 3.2 |
tests/unit/schema/zod/features/unions-and-intersections.test.ts, tests/unit/schema/zod/features/primitives.test.ts, tests/unit/schema/zod/node-helpers.test.ts |
Closes issue #143 |
z.tuple([...]) |
Emits tuple-aware arrays with prefixItems, items: false, and fixed item counts |
3.1, 3.2 |
tests/unit/schema/zod/node-helpers.test.ts, tests/unit/schema/zod/zod-converter-helpers.test.ts |
3.0 downgrades happen in version finalization |
| Shared imported query schemas | Per-parameter schemas retain $ref / allOf detail |
3.0, 3.1, 3.2 |
tests/unit/schema/typescript/schema-content.test.ts, tests/integration/generator/zod4-support.test.ts |
Closes issue #93 |
Required fields in @queryParams object schemas |
Per-parameter required: true matches parent schema required list |
3.0, 3.1, 3.2 |
tests/unit/schema/typescript/schema-content.test.ts, tests/integration/generator/zod4-support.test.ts |
Closes issue #94 |
Exported z.infer<typeof Schema> aliases in pure-Zod mode |
No duplicate component unless alias is explicitly referenced | 3.0, 3.1, 3.2 |
tests/unit/schema/zod/zod-converter.test.ts, tests/integration/generator/zod4-support.test.ts |
Closes issue #96 |
import { z } from "zod/v4" |
Parsed the same as zod import path when the local binding is z |
3.0, 3.1, 3.2 |
tests/unit/schema/zod/zod-converter.test.ts, tests/integration/generator/zod4-support.test.ts, tests/integration/generator/zod4-support.test.ts |
Also covered in a Pages Router fixture |
z.discriminatedUnion() with spread base shapes |
Member schemas that spread a shared base shape (e.g. z.object({ ...baseShape, type: z.literal('dog') }).meta({ id: 'Dog' })) emit correct component schemas containing all spread properties, not only the discriminator field. |
3.0, 3.1, 3.2 |
tests/unit/regressions/zod-issue-141-discriminated-union-spread.test.ts |
Closes issue #141 |
| Fixture | Purpose |
|---|---|
tests/fixtures/projects/next/app-router/zod-only-coverage |
App Router Zod-first coverage for top-level helpers, transformed query params, nullable helper output, and pure-Zod alias behavior |
tests/fixtures/projects/next/pages-router/zod-flow |
Pages Router coverage for zod/v4 imports and Zod-generated response schemas |
- The generator preserves the strongest OpenAPI-representable base schema for transforms and refinements, but it does not serialize arbitrary runtime predicates.
- The runtime-assisted Zod 4 export path is intentionally selective. It currently targets features such as
coerce,pipe,templateLiteral,stringbool, and static.meta(...)payloads, while the broader converter still runs through AST analysis. @authmetadata currently emits alternative security requirements for comma-separated values. Combined requirements and advanced scheme fields should still be modeled in templates or reusable OpenAPI fragments.- Response inference is selective and best-effort. It supports named response types, inline object responses, multiple return paths, and
204responses, but explicit@responsetags remain the most deterministic option when stable component names matter.