Skip to content

Releases: colinhacks/zod

v4.6.7

Choose a tag to compare

@github-actions github-actions released this 12 Oct 16:30
b500a2e

Commits:

v4.6.6

Choose a tag to compare

@github-actions github-actions released this 11 Oct 16:19

Six malicious commits pushed to main on September 21 with a compromised git credential were reverted the same day in #6620 and have since been removed from the history of main. None were ever published to npm. The npm provenance for 4.6.6 names 284243f, the pre-rewrite copy of 9c21983 with identical contents.

Commits:

  • 0cd3af5 docs: load the en locale directly in Zod Mini
  • 9d5e60f docs: import locales from zod/locales, not z.locales
  • 34a626f docs: tighten the z.locales tree-shaking note
  • 74304ce release: stage npm versions instead of publishing them (#6622)
  • b2ef983 docs: add Code for Japan to bronze sponsors
  • 8fb2faf docs: crop the Code for Japan logo and list it first in bronze
  • bacad51 docs: square the Code for Japan logo to match the other bronze tiles
  • c42596b release: hold the run on the release-approval environment until the staged versions are approved (#6638)
  • a2ba4a6 fix(types): declare record output and input as members, not base type arguments (#6645)
  • a0ebf97 fix(v4): check array, set and map size bounds before parsing elements (#6652)
  • 529f78d 4.6.6
  • 9c21983 ci: stage at verbose so a failed OIDC token exchange says why

v4.6.5

Choose a tag to compare

@github-actions github-actions released this 13 Sep 23:25

Commits:

  • d2b135c docs: add the 4.6.x patch highlights to the 4.6 post
  • f1448f7 docs: fold the 4.6.x patch highlights into the 4.6 post's own sections
  • de65a5c docs: lead the properties section with the check and add a Zod Mini tab (#6598)
  • 56222cd feat(instanceof): key the .properties() shape off the instance type (#6600)
  • ca0229a Revert "feat: add z.currencyCode() over a vendored ISO 4217 list, refreshed weekly by CI (#6595)"
  • cc4cd4e Revert "Revert "feat: add z.currencyCode() over a vendored ISO 4217 list, refreshed weekly by CI (#6595)""
  • 0f3f5ee 4.6.5
  • 59bbc03 chore: re-pin the integration peers to the workspace zod after the 4.6.5 bump

v4.6.4

Choose a tag to compare

@github-actions github-actions released this 13 Sep 00:12

A patch on top of 4.6.3.

  • d6bc1e30 feat: add z.currencyCode() over a vendored ISO 4217 list, refreshed weekly by CI (#6595)
  • ad32d751 perf: z.url() rejects an invalid URL with URL.canParse() instead of a throwing constructor, about 50x faster; fewer allocations on the validation path (#6588)
  • 2bb08717 chore: re-pin the integration peers to the workspace zod after the 4.6.4 bump
  • f6e1701a chore(deps): bump next to 15.5.25 and vite to 7.3.6 (#6153)

v4.6.3

Choose a tag to compare

@github-actions github-actions released this 12 Sep 22:24

A patch on top of 4.6.2.

  • 413cce9a fix(v4): make z.properties() a check again (#6594) — removes the standalone z.properties() schema from 4.6.0; z.instanceof().properties() and .check(...z.properties()) are unchanged
  • 75d63ee1 docs: show only the .properties() method form in the 4.6 post
  • 46da9572 docs: match the error-message examples to what the parsers emit

v4.6.2

Choose a tag to compare

@github-actions github-actions released this 10 Sep 21:44

A patch on top of 4.6.1.

v4.6.1

Choose a tag to compare

@github-actions github-actions released this 09 Sep 21:50

A patch on top of 4.6.0.

v4.6.0

Choose a tag to compare

@github-actions github-actions released this 09 Sep 19:25

Zod 4.6 is now available.

npm install zod@latest

At a glance:

  • .validate() — checks input validity without building a result (up to 35x faster than .safeParse().success on a compiled schema)
  • z.instanceof().properties() — validates properties of an instance
  • fromJSONSchema() — enforces six validation keywords it used to ignore
  • z.iban() — electronic-format IBAN plus mod-97 checksum
  • z.withParser() — installs a parser generated elsewhere, for environments without new Function
  • Faster CommonJS — drops the getter on every export (~3x faster z.validate() under require)
  • Memory retention in recursive schemas — releases the parsed input, fixing a 4.5 out-of-memory regression
  • @zod/mini — Zod Mini as a standalone package, versioned in lockstep with zod since 4.5

.validate()

Standalone boolean validation, in Zod, Zod Mini, and Zod Core. It answers "is this input valid?" without constructing a ZodError, which makes rejection cheap. The return type is a guard on the schema's input type.

z.validate(z.string(), "hi"); // true
z.validate(z.string(), 42);   // false

It is a method on Zod Classic schemas too. (#6547)

const Player = z.object({
  username: z.string(),
  xp: z.number(),
});

if (Player.validate(data)) {
  data.username; // narrowed
}

In conjunction with z.compile(), this can be up to 35x faster than .safeParse().success on invalid input.

Time per call on invalid input, schemas compiled with z.compile(), safeParse().success as a gray bar with .validate() as a blue bar inside it: a union of 3 objects 28 ns (34.9x faster), an array of 10 strings 23 ns (24.6x), a 3-element tuple 28 ns (16.1x), a 5-key object 21 ns (16.3x), a discriminated union of 3 21 ns (15.7x), z.number() 19 ns (13.8x), z.string() 19 ns (13.2x), z.boolean() 19 ns (13.7x); up to 34.9x faster

Time per call on invalid input, compiled with z.compile() — lower is better (benchmark)

Uncompiled schemas

Without compilation it is up to 5.9x faster. The saving is the result object: .safeParse() allocates one with an accessor pair on every call, and .validate() allocates nothing.

Time per call on invalid input, plain schemas, safeParse().success as a gray bar with .validate() as a blue bar inside it: a union of 3 objects 659 ns (1.5x faster), an array of 10 strings 211 ns (2.3x), a 3-element tuple 166 ns (2.5x), a discriminated union of 3 93 ns (3.3x), a 5-key object 75 ns (3.9x), z.number() 47 ns (5.3x), z.string() 42 ns (5.8x), z.boolean() 42 ns (5.9x); up to 5.9x faster

Time per call on invalid input, plain schemas — lower is better (benchmark)

Both charts measure the failure path. The key feature of .validate() is that it can short-circuit on the first issue it encounters, instead of aggregating a full ZodIssue[] array.

Note

Async refinements are covered by .validateAsync().

z.properties()

A new API for validating specific properties of an object. Unlike z.object() it validates in-place, so it plays nice with class instances. (#6536)

const responseLike = z.properties({ status: z.number().min(200).max(299) });

responseLike.parse(new Response("ok", { status: 200 }));  // ✅ a real Response
responseLike.parse({ status: 204 });                      // ✅ a plain object

A corresponding .properties() method has been added to ZodInstanceOf.

Zod

const okResponse = z.instanceof(Response).properties({
  ok: z.literal(true),
  status: z.number().min(200).max(299),
});

Zod Mini

const okResponse = z.instanceof(Response).check(...z.properties({
  ok: z.literal(true),
  status: z.number().check(z.minimum(200), z.maximum(299)),
}));

The input comes back untouched, so the prototype survives and the methods still work. That is the part z.object() cannot do: it would hand back a plain object and the Response would be gone.

const res = await fetch("/api/user");

okResponse.parse(res) === res; // ✅ true

fromJSONSchema()

Six additional JSON Schema keywords are now supported in z.fromJSONSchema(). (#6535)

const schema = z.fromJSONSchema({
  type: "object",
  minProperties: 2,      // also maxProperties
});

schema.parse({ a: 1 });        // ❌ too few properties
schema.parse({ a: 1, b: 2 });  // ✅

Both property bounds count the input's own keys. Array uniqueness is structural, so [{ a: 1 }, { a: 1 }] is a duplicate.

z.fromJSONSchema({ type: "array", uniqueItems: true }).parse([{ a: 1 }, { a: 1 }]); // ❌

z.fromJSONSchema({
  type: "array",
  contains: { type: "number" },   // also minContains and maxContains
  minContains: 2,
}).parse(["a", 2]);               // ❌ only one number

z.iban()

A new string format: an IBAN in electronic format, with a valid ISO 7064 MOD 97-10 checksum. (#6571)

z.iban().parse("DE89370400440532013000"); // ✅
z.iban().parse("DE89370400440532013001"); // ❌ bad checksum

z.withParser()

z.compile() builds its parser with new Function, which a strict Content Security Policy blocks. z.withParser() is that installer on its own: it takes a parser generated somewhere else, at build time or by a native compiler, and installs it under the same contract. (#6575)

const Player = z.object({ username: z.string(), xp: z.number() });

// isPlayer is a type guard your build step generated
const Fast = z.withParser(Player, (input) =>
  isPlayer(input) ? { username: input.username, xp: input.xp } : z.INVALID
);

The supplied parser owns the whole result, so it has to return what the schema would have returned. This one rebuilds the object rather than handing back its input, because z.object() strips unknown keys. Returning z.INVALID hands the input to the runtime, which stays the only source of ZodErrors.

Faster CommonJS

TypeScript compiles a re-export to a getter, and 252 of the 255 exports on Zod 4.5's CommonJS entrypoint were getters. V8 could not see a constant callee behind one, so it could not inline the call. The 4.6 build emits plain properties and freezes the exports object. On a compiled schema, z.validate() under require is about 3x faster than it was in Zod 4.5. (#6564)

const { z } = require("zod");
const CompiledPlayer = z.compile(Player);

z.validate(CompiledPlayer, data); // ~3x faster than Zod 4.5

Only calls through the namespace were affected. A method call like Player.safeParse(data) never reads the exports object, and the ESM build is unchanged.

Memory retention in recursive schemas

A recursive schema held the input and output of its last parse until the next parse replaced it, so one long-lived schema pinned every object it had touched. Zod 4.4 released that input and Zod 4.5 did not, which surfaced as an out-of-memory failure on a repository-wide lint run. The parse state is weak throughout now: one parse of a 29k-node tree retains 2.2 MB where it used to retain 10.1 MB, and recursive parses give up about 6% for it. (#6572)

const Category = z.object({
  name: z.string(),
  get children() {
    return z.array(Category);
  },
});

Bug fixes

⚠️ Error maps run on the first read of error

Because safeParse() now builds its error lazily, error maps — global, locale, and per-schema error — run when result.error is first read, not at parse time. Code that swaps z.config() between the parse and the read gets the newer configuration. (#6519)

const result = schema.safeParse(12);
z.config(z.locales.fr());
result.error.issues[0].message; // French in 4.6, English in 4.5

An error map with a side effect never runs if nothing reads the error. Throwing parses are unaffected — .parse() builds and throws its error immediately, never takes the lazy path, and its stack still points at your call site.

⚠️ z.emoji() rejects component-only strings

Unicode's Emoji_Component property covers the pieces that attach to an emoji, so z.emoji() accepted "123", "#", "*", and a lone zero-width joiner, variation selector, or skin tone modifier. The pattern now requires at least one pictograph, re...

Read more

v4.5.4

Choose a tag to compare

@github-actions github-actions released this 29 Aug 17:55

Commits:

v4.5.3

Choose a tag to compare

@colinhacks colinhacks released this 29 Aug 17:51

Commits:

  • e6b6ab3 docs(blog): widen the z.compile example to a 20-property schema
  • 87d6464 fix(docs): drop the OG description when the title wraps past two lines
  • 99fce39 bench(v4): z.compile() against zod-compiler (#6499)
  • e3a695b docs(v4): record the email regex and container output-shape findings under Open
  • 7e24a24 docs(blog): drop the reading time and put a GitHub link in the navbar
  • eab51ff fix(v4): emit record numeric keys as strings in toJSONSchema (#6497)