note: no summary for this older release
We summarize recent releases. The original release notes for this version are below.
release-notesgithub.com/colinhacks/zod · 303 lines
- This is Zod's biggest release since 4.0. It addresses several of Zod's longest-standing feature requests.
- ## `z.fromJSONSchema()`
- Convert JSON Schema to Zod ([#5534](https://github.com/colinhacks/zod/pull/5534), [#5586](https://github.com/colinhacks/zod/pull/5586))
- You can now convert JSON Schema definitions directly into Zod schemas. This function supports JSON Schema `"draft-2020-12"`, `"draft-7"`, `"draft-4"`, and OpenAPI 3.0.
- ```typescript
- import * as z from "zod";
- const schema = z.fromJSONSchema({
- type: "object",
- properties: {
- name: { type: "string", minLength: 1 },
- age: { type: "integer", minimum: 0 },
- },
- required: ["name"],
- });
- schema.parse({ name: "Alice", age: 30 }); // ✅
- ```
- The API should be considered experimental. There are no guarantees of 1:1 "round-trip soundness": `MySchema` > `z.toJSONSchema()` > `z.fromJSONSchema()`. There are several features of Zod that don't exist in JSON Schema and vice versa, which makes this virtually impossible.
- Features supported:
- - All primitive types (`string`, `number`, `integer`, `boolean`, `null`, `object`, `array`)
- - String formats (`email`, `uri`, `uuid`, `date-time`, `date`, `time`, `ipv4`, `ipv6`, and more)
- - Composition (`anyOf`, `oneOf`, `allOf`)
- - Object constraints (`additionalProperties`, `patternProperties`, `propertyNames`)
- - Array constraints (`prefixItems`, `items`, `minItems`, `maxItems`)
- - `$ref` for local references and circular schemas
- - Custom metadata is preserved
- ## `z.xor()` — exclusive union ([#5534](https://github.com/colinhacks/zod/pull/5534))
- A new exclusive union type that requires **exactly one** option to match. Unlike `z.union()` which passes if *any* option matches, `z.xor()` fails if zero or more than one option matches.
- ```typescript
- const schema = z.xor([z.string(), z.number()]);
- schema.parse("hello"); // ✅
- schema.parse(42); // ✅
- schema.parse(true); // ❌ zero matches
- ```
- When converted to JSON Schema, `z.xor()` produces `oneOf` instead of `anyOf`.
- ## `z.looseRecord()` — partial record validation ([#5534](https://github.com/colinhacks/zod/pull/5534))
- A new record variant that only validates keys matching the key schema, passing through non-matching keys unchanged. This is used to represent `patternProperties` in JSON Schema.
- ```typescript
- const schema = z.looseRecord(z.string().regex(/^S_/), z.string());
- schema.parse({ S_name: "John", other: 123 });
- // ✅ { S_name: "John", other: 123 }
- // only S_name is validated, "other" passes through
- ```
- ## `.exactOptional()` — strict optional properties ([#5589](https://github.com/colinhacks/zod/pull/5589))
- A new wrapper that makes a property *key-optional* (can be omitted) but does **not** accept `undefined` as an explicit value.
- ```typescript
- const schema = z.object({
- a: z.string().optional(), // accepts `undefined`
- b: z.string().exactOptional(), // does not accept `undefined`
- });
- schema.parse({}); // ✅
- schema.parse({ a: undefined }); // ✅
- schema.parse({ b: undefined }); // ❌
- ```
- This makes it possible to accurately represent the full spectrum of optionality expressible using [`exactOptionalPropertyTypes`](https://www.typescriptlang.org/tsconfig/exactOptionalPropertyTypes.html).
- ## `.apply()`
- A utility method for applying arbitrary transformations to a schema, enabling cleaner schema composition. ([#5463](https://github.com/colinhacks/zod/pull/5463))
- ```typescript
- const setCommonChecks = <T extends z.ZodNumber>(schema: T) => {
- return schema.min(0).max(100);
- };
- const schema = z.number().apply(setCommonChecks).nullable();
- ```
- ## `.brand()` cardinality
- The `.brand()` method now accepts a second argument to control whether the brand applies to input, output, or both. Closes [#4764](https://github.com/colinhacks/zod/issues/4764), [#4836](https://github.com/colinhacks/zod/issues/4836).
- ```typescript
- // output only (default)
- z.string().brand<"UserId">(); // output is branded (default)
- z.string().brand<"UserId", "out">(); // output is branded
- z.string().brand<"UserId", "in">(); // input is branded
- z.string().brand<"UserId", "inout">(); // both are branded
- ```
- ## Type predicates on `.refine()` ([#5575](https://github.com/colinhacks/zod/pull/5575))
- The `.refine()` method now supports type predicates to narrow the output type:
- ```typescript
- const schema = z.string().refine((s): s is "a" => s === "a");
- type Input = z.input<typeof schema>; // string
- type Output = z.output<typeof schema>; // "a"
- ```
- ## `ZodMap` methods: `min`, `max`, `nonempty`, `size` ([#5316](https://github.com/colinhacks/zod/pull/5316))
- `ZodMap` now has parity with `ZodSet` and `ZodArray`:
- ```typescript
- const schema = z.map(z.string(), z.number())
- .min(1)
- .max(10)
- .nonempty();
- schema.size; // access the size constraint
- ```
- ## `.with()` alias for `.check()` ([359c0db](https://github.com/colinhacks/zod/commit/359c0db6))
- A new `.with()` method has been added as a more readable alias for `.check()`. Over time, more APIs have been added that don't qualify as "checks". The new method provides a readable alternative that doesn't muddy semantics.
- ```typescript
- z.string().with(
- z.minLength(5),
- z.toLowerCase()
- );
- // equivalent to:
- z.string().check(
- z.minLength(5),
- z.trim(),
- z.toLowerCase()
- );
- ```
- ## `z.slugify()` transform
- Transform strings into URL-friendly slugs. Works great with `.with()`:
- ```typescript
- // Zod
- z.string().slugify().parse("Hello World"); // "hello-world"
- // Zod Mini
- // using .with() for explicit check composition
- z.string().with(z.slugify()).parse("Hello World"); // "hello-world"
- ```
- ## `z.meta()` and `z.describe()` in Zod Mini ([947b4eb](https://github.com/colinhacks/zod/commit/947b4eb2))
- Zod Mini now exports `z.meta()` and `z.describe()` as top-level functions for adding metadata to schemas:
- ```typescript
- import * as z from "zod/mini";
- // add description
- const schema = z.string().with(
- z.describe("A user's name"),
- );
- // add arbitrary metadata
- const schema2 = z.number().with(
- z.meta({ deprecated: true })
- );
- ```
- ## More ergonomic intersections https://github.com/colinhacks/zod/pull/5587
- When intersecting schemas that include `z.strictObject()`, Zod 4 now only rejects keys that are unrecognized by *both* sides of the intersection. Previously, any unrecognized key from either side would cause an error.
- This means keys that are recognized by at least one side of the intersection will now pass validation:
- ```ts
- const A = z.strictObject({ a: z.string() });
- const B = z.object({ b: z.string() });
- const C = z.intersection(A, B);
- // Keys recognized by either side now work
- C.parse({ a: "foo", b: "bar" }); // ✅ { a: "foo", b: "bar" }
- // Extra keys are stripped (follows strip behavior from B)
- C.parse({ a: "foo", b: "bar", c: "extra" }); // ✅ { a: "foo", b: "bar" }
- ```
- When both sides are strict, only keys unrecognized by *both* sides will error:
- ```ts
- const A = z.strictObject({ a: z.string() });
- const B = z.strictObject({ b: z.string() });
- const C = z.intersection(A, B);
- // Keys recognized by either side work
- C.parse({ a: "foo", b: "bar" }); // ✅
- // Keys unrecognized by BOTH sides error
- C.parse({ a: "foo", b: "bar", c: "extra" });
- // ❌ ZodError: Unrecognized key: "c"
- ```
- ## New locales
- - Armenian (`am`) ([#5531](https://github.com/colinhacks/zod/pull/5531))
- - Uzbek (`uz`) ([#5519](https://github.com/colinhacks/zod/pull/5519))
- ```ts
- import * as z from "zod";
- import { uz } from "zod/locales";
- z.config(uz());
- ```
- <br/><br/><br/>
- ## Bug fixes
- All of these changes fix soundness issues in Zod. As with any bug fix there's some chance of breakage if you were intentionally or unintentionally relying on this unsound behavior.
- ### ⚠️ `.pick()` and `.omit()` disallowed on object schemas containing refinements ([#5317](https://github.com/colinhacks/zod/pull/5317))
- Using `.pick()` or `.omit()` on object schemas with refinements now throws an error. Previously, this would silently drop the refinements, leading to unexpected behavior.
- ```typescript
- const schema = z.object({
- password: z.string(),
- confirmPassword: z.string(),
- }).refine(data => data.password === data.confirmPassword);
- schema.pick({ password: true });
- // 4.2: refinement silently dropped ⚠️
- // 4.3: throws error ❌
- ```
- **Migration**: The easiest way to migrate is to create a new schema using the `shape` of the old one.
- ```ts
- const newSchema = z.object(schema.shape).pick({ ... })
- ```
- ### ⚠️ overwriting properties with`.extend()` disallowed on object schemas with refinements ([#5317](https://github.com/colinhacks/zod/pull/5317))
- Similarly, `.extend()` will throws on schemas with refinements *if* you are overwriting existing properties.
- ```typescript
- const schema = z.object({
- a: z.string()
- }).refine(/* ... */);
- schema.extend({ a: z.number() }); // 4.3: throws error ❌
- ```
- Instead you can use `.safeExtend()`, which statically ensures that you aren't changing the type signature of any pre-existing properties.
- ```typescript
- const schema = z.object({
- a: z.string(),
- }).refine(/* ... */);
- schema.safeExtend({
- a: z.string().min(5).max(10)
- }); // ✅ allows overwrite, preserves refinement
- ```
- ### ⚠️ Stricter object masking methods ([#5581](https://github.com/colinhacks/zod/pull/5581))
- Object masking methods (`.pick()`, `.omit()`) now validate that the keys provided actually exist in the schema:
- ```typescript
- const schema = z.object({ a: z.string() });
- // 4.3: throws error for unrecognized keys
- schema.pick({ nonexistent: true });
- // error: unrecognized key: "nonexistent"
- ```
- <br/><br/><br/>
- ## Additional changes
- - Fixed JSON Schema generation for `z.iso.time` with minute precision ([#5557](https://github.com/colinhacks/zod/pull/5557))
- - Fixed error details for tuples with extraneous elements ([#5555](https://github.com/colinhacks/zod/pull/5555))
- - Fixed `includes` method params typing to accept `string | $ZodCheckIncludesParams` ([#5556](https://github.com/colinhacks/zod/pull/5556))
- - Fixed numeric formats error messages to be inclusive ([#5485](https://github.com/colinhacks/zod/pull/5485))
- - Fixed `implementAsync` inferred type to always be a promise ([#5476](https://github.com/colinhacks/zod/pull/5476))
- - Tightened E.164 regex to require a non-zero leading digit and 7–15 digits total ([#5524](https://github.com/colinhacks/zod/pull/5524))
- - Fixed Dutch (nl) error strings ([#5529](https://github.com/colinhacks/zod/pull/5529))
- - Convert `Date` instances to numbers in `minimum`/`maximum` checks ([#5351](https://github.com/colinhacks/zod/pull/5351))
- - Improved numeric keys handling in `z.record()` ([#5585](https://github.com/colinhacks/zod/pull/5585))
- - Lazy initialization of `~standard` schema property ([#5363](https://github.com/colinhacks/zod/pull/5363))
- - Functions marked as `@__NO_SIDE_EFFECTS__` for better tree-shaking ([#5475](https://github.com/colinhacks/zod/pull/5475))
- - Improved metadata tracking across child-parent relationships ([#5578](https://github.com/colinhacks/zod/pull/5578))
- - Improved locale translation approach ([#5584](https://github.com/colinhacks/zod/pull/5584))
- - Dropped id uniqueness enforcement at registry level ([#5574](https://github.com/colinhacks/zod/pull/5574))