* fix(v4): preserve callsites in parse stack traces * fix(v4): await the async parse wrappers so the callee stays on the chain `Error.captureStackTrace(e, callee)` is skip-until-seen: V8 discards frames until it meets `callee`, and if it never meets it, it discards them all. An async function that returns its inner promise without awaiting has already left the async chain by the time the rejection is built, so its frame is not there to be found and `err.stack` comes back as the message line alone. That made all four async codec paths return an empty stack where main returned a usable one, which is the opposite of this branch's purpose. Instance `.parseAsync()` had the same defect on main already; it is fixed here too, since it is the same one-word change and the same invariant. The existing coverage could not catch this. An async function body runs synchronously up to its first `await`, so `z.string()` never suspends and its callee is always still on the chain. The new test uses an async refinement and an async codec, and fails without this fix. * test(treeshake): re-measure the ceilings against the current base The three raises were measured against a base five commits stale. Merging current main puts `zod-mini-object` at exactly its ceiling and the other two at 18 bytes, back in the 16-21 band this file's own comment records as having broken within a day. Re-measured on the merged tree: 2808 / 3128 / 4301, so the ceilings move to 28 bytes of headroom each, which is what the comment claims. * test(treeshake): attribute the ceiling raise to the right cause The comment booked the whole 2813 / 3130 / 4288 raise to the callee threading. Measured against the merge base, this branch costs +13 / +17 / +16; the remainder is drift on main since those numbers were last set, and main alone had already reached 4285 against its own 4288. A file whose job is recording what caused each raise should say which part was which.
Zod
TypeScript-first schema validation with static type inference
by @colinhacks
Read the docs →
What is Zod?
Zod is a TypeScript-first validation library. Define a schema and parse some data with it. You'll get back a strongly typed, validated result.
import * as z from "zod";
const User = z.object({
name: z.string(),
});
// some untrusted data...
const input = {
/* stuff */
};
// the parsed result is validated and type safe!
const data = User.parse(input);
// so you can use it with confidence :)
console.log(data.name);
Features
- Zero external dependencies
- Works in Node.js and all modern browsers
- Tiny:
2kbcore bundle (gzipped) - Immutable API: methods return a new instance
- Concise interface
- Works with TypeScript and plain JS
- Built-in JSON Schema conversion
- Extensive ecosystem
Installation
npm install zod
Basic usage
Before you can do anything else, you need to define a schema. For the purposes of this guide, we'll use a simple object schema.
import * as z from "zod";
const Player = z.object({
username: z.string(),
xp: z.number(),
});
Parsing data
Given any Zod schema, use .parse to validate an input. If it's valid, Zod returns a strongly-typed deep clone of the input.
Player.parse({ username: "billie", xp: 100 });
// => returns { username: "billie", xp: 100 }
Note — If your schema uses certain asynchronous APIs like async refinements or transforms, you'll need to use the .parseAsync() method instead.
const schema = z.string().refine(async (val) => val.length <= 8);
await schema.parseAsync("hello");
// => "hello"
AOT compilation
Canary only — compilation has not shipped in a stable release yet. Install with npm install zod@canary.
For hot validation paths, z.compile(schema) returns a schema clone with an ahead-of-time compiled fast path. Valid inputs take the compiled path; invalid inputs fall back to the regular parser so error reporting stays identical.
Across a 55-schema benchmark the median speedup is 2.4x, and it scales with how much work the schema does per parse: a large array of objects is ~9x, a 20-key object ~9x, a nested object ~4.5x, while a bare z.string() gains nothing — compilation removes per-node dispatch and allocation, and a single typeof has none to remove.
const CompiledPlayer = z.compile(Player);
CompiledPlayer.parse({ username: "billie", xp: 100 });
To enable compilation globally for schemas constructed after import:
import "zod/compile"; // place before modules that define schemas
Things to know:
- Compilation uses
new Function. Global mode is automatically disabled whenz.config({ jitless: true })is set (e.g. CSP environments); callingz.compile()directly is an explicit opt-in. - Schemas with async refinements or transforms can't be compiled —
z.compile()throwsZodCompileAsyncError; other unsupported constructs throwZodCompileUnsupportedError. In global mode such schemas silently keep using the regular parser. - On invalid input, refinements and transforms may run twice (fast path, then fallback).
- Deriving a new schema from a compiled one (
.refine(),.extend(), etc.) returns an uncompiled schema — compile the final schema.
See compile docs for details.
Handling errors
When validation fails, the .parse() method will throw a ZodError instance with granular information about the validation issues.
try {
Player.parse({ username: 42, xp: "100" });
} catch (err) {
if (err instanceof z.ZodError) {
err.issues;
/* [
{
expected: 'string',
code: 'invalid_type',
path: [ 'username' ],
message: 'Invalid input: expected string'
},
{
expected: 'number',
code: 'invalid_type',
path: [ 'xp' ],
message: 'Invalid input: expected number'
}
] */
}
}
To avoid a try/catch block, you can use the .safeParse() method to get back a plain result object containing either the successfully parsed data or a ZodError. The result type is a discriminated union, so you can handle both cases conveniently.
const result = Player.safeParse({ username: 42, xp: "100" });
if (!result.success) {
result.error; // ZodError instance
} else {
result.data; // { username: string; xp: number }
}
Note — If your schema uses certain asynchronous APIs like async refinements or transforms, you'll need to use the .safeParseAsync() method instead.
const schema = z.string().refine(async (val) => val.length <= 8);
await schema.safeParseAsync("hello");
// => { success: true; data: "hello" }
Inferring types
Zod infers a static type from your schema definitions. You can extract this type with the z.infer<> utility and use it however you like.
const Player = z.object({
username: z.string(),
xp: z.number(),
});
// extract the inferred type
type Player = z.infer<typeof Player>;
// use it in your code
const player: Player = { username: "billie", xp: 100 };
In some cases, the input & output types of a schema can diverge. For instance, the .transform() API can convert the input from one type to another. In these cases, you can extract the input and output types independently:
const mySchema = z.string().transform((val) => val.length);
type MySchemaIn = z.input<typeof mySchema>;
// => string
type MySchemaOut = z.output<typeof mySchema>; // equivalent to z.infer<typeof mySchema>
// number