v24.0.0
v24 is for Ashley
Version 24 introduces initial support for Zod 4, requiring at least version 3.25.35, but due to the special versioning strategy of Zod all imports must be changed to zod/v4. The framework simplifies the IOSchema type to explicitly require schemas that resolve to objects. Documentation generation now uses Zod 4’s native toJSONSchema() method, with custom overrides to align with OpenAPI 3.1, and the numericRange option has been removed. The Depicter type changed its signature, and brandHandling is now implemented as a postprocessing step. The optionalPropStyle option was removed from the Integration constructor. Using the Zod's internal storage for examples: method .example() changed its argument from z.input<> to z.output<> which is breaking for transforming schemas, so that examples of its input side must be set before the transformation. The , restored in 24.1.0.getExamples() helper removed in favour of .meta()?.examples
Breaking changes
- Switched to Zod 4:
- Minimum supported version of
zodis 3.25.35, BUT imports MUST be fromzod/v4;- Read the Explanation of the versioning strategy;
- Express Zod API, however, is not aiming to support both Zod 3 and Zod 4 simultaneously due to:
- incompatibility of data structures;
- operating composite schemas (need to avoid mixing schemas of different versions);
- the temporary nature of this transition;
- the advantages of Zod 4 that provide opportunities to simplifications and corrections of known issues.
IOSchematype had to be simplified down to a schema resulting to anobject, but not anarray;- Refer to Migration guide on Zod 4 for adjusting your schemas;
- Minimum supported version of
- Changes to
ZodType::example()(Zod plugin method):- Now acts as an alias for
ZodType::meta({ examples }); - The argument has to be the output type of the schema (used to be the opposite):
- This change is only breaking for transforming schemas;
- In order to specify an example for an input schema the
.example()method must be called before.transform();
- Now acts as an alias for
- The transforming proprietary schemas
ez.dateIn()andez.dateOut()now accept metadata as its argument:- This allows to set examples before transformation (
ez.dateIn()) and to avoid the examples "branding";
- This allows to set examples before transformation (
- Changes to
Documentation:- Generating Documentation is mostly delegated to Zod 4
z.toJSONSchema(); - Express Zod API implements some overrides and improvements to fit it into OpenAPI 3.1 that extends JSON Schema;
- The
numericRangeoption removed fromDocumentationclass constructor argument; - The
Depictertype signature changed: became a postprocessing function returning an overridden JSON Schema;
- Generating Documentation is mostly delegated to Zod 4
- Changes to
Integration:- The
optionalPropStyleoption removed fromIntegrationclass constructor: - Use
.optional()to add question mark to the object property as well asundefinedto its type; - Use
.or(z.undefined())to addundefinedto the type of the object property; - See the reasoning;
- Properties assigned with
z.any()orz.unknown()schema are now typed as required:- Read the details here;
- Added types generation for
z.never(),z.void()andz.unknown()schemas; - The fallback type for unsupported schemas and unclear transformations in response changed from
anytounknown;
- The
- The argument of
ResultHandler::handleris now discriminated: eitheroutputorerrorisnull, not both; - The
getExamples()public helper removed —use, restored in 24.1.0;.meta()?.examplesinstead - Added the new proprietary schema
ez.buffer(); - The
ez.file()schema removed: usez.string(),z.base64(),ez.buffer()or their union;
Migration
Consider the automated migration using the built-in ESLint rule.
// eslint.config.mjs — minimal ESLint 9 config to apply migrations automatically using "eslint --fix"
import parser from "@typescript-eslint/parser";
import migration from "express-zod-api/migration";
export default [
{ languageOptions: { parser }, plugins: { migration } },
{ files: ["**/*.ts"], rules: { "migration/v24": "error" } },
];- import { z } from "zod";
+ import { z } from "zod/v4"; input: z.string()
+ .example("123")
.transform(Number)
- .example("123")- ez.dateIn().example("2021-12-31");
+ ez.dateIn({ examples: ["2021-12-31"] });
- ez.file("base64");
+ z.base64();
- ez.file("buffer");
+ ez.buffer();