2. Objects, Collections, Unions & Wrapper Combinators
This module documents Zod v4.6.7's composite container schemas (ZodObject, ZodArray, ZodTuple, ZodRecord, ZodMap, ZodSet), algebraic union and intersection combinators (ZodUnion, ZodXor, ZodDiscriminatedUnion, ZodIntersection), wrapper modifiers (ZodOptional, ZodExactOptional, ZodNullable, ZodDefault, ZodPrefault, ZodNonOptional, ZodCatch, ZodReadonly, $ZodBranded), recursive/custom schemas (z.lazy, z.custom, z.instanceof, z.json), and runtime function/promise wrappers (ZodFunction, ZodPromise).
Source Code
from drawlib import canvas
from drawlib.diagrams.architecture import ArchitectureDiagram, Node, NodeGroup, PhosphorIcon
from drawlib.lines import line
from drawlib.styles import Styles
from drawlib.text import text
canvas.clear()
canvas.setup(width=184, height=106, dpi=150)
text(
(92, 100.5),
"Zod v4.6.7 Composite Schemas Hierarchy (Objects, Collections & Unions)",
style=Styles.DarkBold.patch(text_size=13.0),
)
d = ArchitectureDiagram(
node_style=Styles.PrimaryBold,
node_text_style=Styles.DarkBold.patch(text_size=10.0),
edge_style=Styles.PrimaryBold,
edge_text_style=Styles.DarkBold.patch(text_size=10.0),
node_card_style=Styles.Neutral,
)
# Root Base Class
root = d.add(
Node(
"ZodType<Output, Input> (Container Base + Memoized Shape Resolution)\nparse / safeParse / check / toJSONSchema / ~standard",
width=102,
height=12.5,
icon=PhosphorIcon.CUBE,
icon_size=5.8,
style=Styles.WhiteBold,
text_style=Styles.WhiteBold.patch(text_size=10.5),
card_style=Styles.PrimaryFlat,
),
xy=(88.0, 84.0),
)
# Group 1: Object Schemas
g_obj = d.add(
NodeGroup(title="Object Schemas ($ZodObjectJIT)", padding=4.5),
xy=(2.0, 4.0),
)
g_obj.add(
Node("ZodObject ($strip)\nz.object(shape)", width=46, height=12.5, icon=PhosphorIcon.BRACKETS_CURLY, icon_size=5.5, style=Styles.WhiteBold, text_style=Styles.WhiteBold.patch(text_size=10.0), card_style=Styles.AccentFlat),
xy=(27.5, 49.0),
)
g_obj.add(
Node("z.strictObject(shape)\ncatchall: z.never()", width=46, height=12.5, icon=PhosphorIcon.LOCK_KEY, icon_size=5.5, card_style=Styles.PrimaryNeutral),
xy=(27.5, 34.0),
)
g_obj.add(
Node("z.looseObject(shape)\ncatchall: z.unknown()", width=46, height=12.5, icon=PhosphorIcon.ARROWS_OUT_SIMPLE, icon_size=5.5, card_style=Styles.Neutral),
xy=(27.5, 19.0),
)
g_obj.add(
Node("Shape Combinators\nextend / pick / omit / partial", width=46, height=12.5, icon=PhosphorIcon.SLIDERS, icon_size=5.5, card_style=Styles.Neutral),
xy=(27.5, 4.0),
)
# Group 2: Collection Schemas
g_col = d.add(
NodeGroup(title="Collection & Key-Value Schemas", padding=4.5),
xy=(61.0, 4.0),
)
g_col.add(
Node("ZodArray<T>\nz.array(el) / min / max", width=48, height=12.5, icon=PhosphorIcon.BRACKETS_SQUARE, icon_size=5.5, card_style=Styles.PrimaryNeutral),
xy=(28.5, 49.0),
)
g_col.add(
Node("ZodTuple<T, Rest>\nz.tuple([...], rest?)", width=48, height=12.5, icon=PhosphorIcon.LIST_NUMBERS, icon_size=5.5, card_style=Styles.Neutral),
xy=(28.5, 34.0),
)
g_col.add(
Node("ZodRecord<K, V>\nrecord / partial / loose", width=48, height=12.5, icon=PhosphorIcon.TABLE, icon_size=5.5, card_style=Styles.Neutral),
xy=(28.5, 19.0),
)
g_col.add(
Node("ZodMap & ZodSet\nz.map(k, v) / z.set(v)", width=48, height=12.5, icon=PhosphorIcon.STACK, icon_size=5.5, card_style=Styles.Neutral),
xy=(28.5, 4.0),
)
# Group 3: Unions & Intersections
g_uni = d.add(
NodeGroup(title="Algebraic Unions & Intersections", padding=4.5),
xy=(122.0, 4.0),
)
g_uni.add(
Node("ZodUnion<Options>\nz.union([...]) / .or()", width=46, height=12.5, icon=PhosphorIcon.GIT_FORK, icon_size=5.5, card_style=Styles.PrimaryNeutral),
xy=(27.5, 49.0),
)
g_uni.add(
Node("ZodDiscriminatedUnion\nz.discriminatedUnion(d, opts)", width=46, height=12.5, icon=PhosphorIcon.LIGHTNING, icon_size=5.5, card_style=Styles.PrimaryNeutral),
xy=(27.5, 34.0),
)
g_uni.add(
Node("ZodXor<Options>\nz.xor([...]) (Exactly 1)", width=46, height=12.5, icon=PhosphorIcon.SPLIT_HORIZONTAL, icon_size=5.5, card_style=Styles.SecondaryNeutral),
xy=(27.5, 19.0),
)
g_uni.add(
Node("ZodIntersection<A, B>\nz.intersection(a, b) / .and()", width=46, height=12.5, icon=PhosphorIcon.INTERSECT, icon_size=5.5, card_style=Styles.Neutral),
xy=(27.5, 4.0),
)
d.draw(xy=(4.0, 2.0))
# Top-down tree bus from ZodType root (center x=92, bottom y=79.5) to the top of each group (y=68.5)
line((92.0, 79.5), (92.0, 74.5), style=Styles.PrimaryBold)
line((33.5, 74.5), (153.5, 74.5), style=Styles.PrimaryBold)
line((33.5, 74.5), (33.5, 68.5), arrow_head="->", style=Styles.PrimaryBold)
line((93.5, 74.5), (93.5, 68.5), arrow_head="->", style=Styles.PrimaryBold)
line((153.5, 74.5), (153.5, 68.5), arrow_head="->", style=Styles.PrimaryBold)
canvas.save()
classZodObject#
ZodObject<Shape, Config> validates JavaScript plain objects against a property Shape map (Record<string, $ZodType>). In Zod v4, object parsing uses a lazy property descriptor for .shape and optional fast-path JIT compilation ($ZodObjectJIT).
interface ZodObject<
out Shape extends core.$ZodShape = core.$ZodLooseShape,
out Config extends core.$ZodObjectConfig = core.$strip,
> extends _ZodType<core.$ZodObjectInternals<Shape, Config>>, core.$ZodObject<Shape, Config>
| Property / Method | Signature | Description |
|---|---|---|
.shape |
Shape |
Returns the raw property schema dictionary defining this object. |
.keyof() |
() => ZodEnum<ToEnum<keyof Shape & string>> |
Returns a ZodEnum of all string property keys in .shape (also available as z.keyof(schema)). |
.extend(shape) |
<U extends $ZodLooseShape>(shape: U) => ZodObject<Extend<Shape, U>, Config> |
Clones the object schema and merges or overwrites properties with shape (replaces deprecated .merge()). |
.safeExtend(shape) |
<U>(shape: SafeExtendShape<Shape, U>) => ZodObject<Extend<Shape, U>, Config> |
Like .extend(), but rejects property overrides at compile time unless the new property's input/output types are assignable to the existing property. |
.pick(mask) |
<M extends Mask<keyof Shape>>(mask: M) => ZodObject<Pick<Shape, ...>, Config> |
Creates a new ZodObject keeping only keys where mask[k] is true. |
.omit(mask) |
<M extends Mask<keyof Shape>>(mask: M) => ZodObject<Omit<Shape, ...>, Config> |
Creates a new ZodObject removing keys where mask[k] is true. |
.partial(mask?) |
(mask?: Mask<keyof Shape>) => ZodObject<...> |
Wraps all (or selected mask) properties in ZodOptional (allowing omitted keys or undefined). |
.exactPartial(mask?) |
(mask?: Mask<keyof Shape>) => ZodObject<...> |
Wraps all (or selected mask) properties in ZodExactOptional (allowing omitted keys, but rejecting explicit { key: undefined }). |
.required(mask?) |
(mask?: Mask<keyof Shape>) => ZodObject<...> |
Wraps all (or selected mask) properties in ZodNonOptional. |
.catchall(schema) |
<T extends SomeType>(schema: T) => ZodObject<Shape, $catchall<T>> |
Validates all unrecognized keys using schema. |
.strict() / .loose() / .strip() |
() => ZodObject<Shape, $strict | $loose | $strip> |
Converts unrecognized-key policy to reject (catchall: z.never()), passthrough (catchall: z.unknown()), or strip (catchall: undefined). |
import { z } from "zod";
const BaseUser = z.object({
id: z.uuid(),
email: z.email(),
name: z.string(),
});
// Extend, pick, omit, and partial combinators
const AdminUser = BaseUser.extend({ permissions: z.array(z.string()) });
const PublicProfile = BaseUser.omit({ email: true });
const PatchUser = BaseUser.partial({ name: true, email: true });
functionz.object / z.strictObject / z.looseObject#
Zod v4 provides three dedicated top-level object constructors corresponding to the three unrecognized-key policies:
function z.object<T extends core.$ZodLooseShape>(shape?: T, params?: string | core.$ZodObjectParams): ZodObject<util.Writeable<T>, core.$strip>
function z.strictObject<T extends core.$ZodLooseShape>(shape: T, params?: string | core.$ZodObjectParams): ZodObject<util.Writeable<T>, core.$strict>
function z.looseObject<T extends core.$ZodLooseShape>(shape: T, params?: string | core.$ZodObjectParams): ZodObject<util.Writeable<T>, core.$loose>
| Constructor | Config Mode | Unrecognized Keys Behavior | JSON Schema additionalProperties |
|---|---|---|---|
z.object(shape, params?) |
core.$strip |
Silently strips unknown properties from the parsed output object. | false (by default in toJSONSchema) |
z.strictObject(shape, params?) |
core.$strict |
Emits an unrecognized_keys issue if any unknown key is present on the input. |
false |
z.looseObject(shape, params?) |
core.$loose |
Preserves unknown properties untouched on the parsed output ([k: string]: unknown). |
{} (or omitted) |
import { z } from "zod";
const StrictPayload = z.strictObject({ action: z.literal("login") });
StrictPayload.safeParse({ action: "login", extra: 123 }).success; // => false (unrecognized_keys)
const LoosePayload = z.looseObject({ action: z.literal("login") });
LoosePayload.parse({ action: "login", traceId: "abc" }); // => { action: "login", traceId: "abc" }
classZodArray#
Constructs a ZodArray<T> schema validating Array.isArray(input) and parsing every element against element: T.
function z.array<T extends core.SomeType>(element: T, params?: string | core.$ZodArrayParams): ZodArray<T>
| Property / Method | Signature | Description |
|---|---|---|
.element / .unwrap() |
T / () => T |
Returns the inner element schema T. |
.min(n, params?) / .max(n, params?) |
(len: number, params?) => this |
Validates input.length >= n (too_small) or input.length <= n (too_big). |
.length(n, params?) |
(len: number, params?) => this |
Validates exact array length input.length === n. |
.nonempty(params?) |
(params?) => this |
Equivalent to .min(1, params). Note: in Zod v4, .nonempty() preserves the type T[] instead of narrowing to [T, ...T[]] (use z.tuple([T], T) for non-empty tuple types). |
import { z } from "zod";
const Tags = z.array(z.string().min(1)).min(1).max(10);
classZodTuple#
Constructs a fixed-length ZodTuple<T, Rest> schema with per-index schemas and an optional variadic rest element schema.
function z.tuple<T extends readonly [core.SomeType, ...core.SomeType[]]>(items: T, params?: string | core.$ZodTupleParams): ZodTuple<T, null>
function z.tuple<T extends readonly [core.SomeType, ...core.SomeType[]], Rest extends core.SomeType>(
items: T,
rest: Rest,
params?: string | core.$ZodTupleParams
): ZodTuple<T, Rest>
| Method | Signature | Description |
|---|---|---|
.rest(restSchema) |
<R extends SomeType>(rest: R) => ZodTuple<T, R> |
Appends a variadic rest element schema for trailing elements ([...T, ...R[]]). |
.partial() |
() => ZodTuple<{ [K in keyof T]: ZodOptional<T[K]> }, Rest> |
Wraps each fixed tuple position in ZodOptional. |
import { z } from "zod";
const Coordinate = z.tuple([z.number(), z.number()], z.string());
type Coord = z.infer<typeof Coordinate>; // [number, number, ...string[]]
classZodRecord#
Constructs a ZodRecord<Key, Value> schema for key-value dictionary objects. Zod v4 distinguishes exhaustive records (z.record), partial enum/union key records (z.partialRecord), and pass-through records (z.looseRecord).
function z.record<Key extends core.$ZodRecordKey, Value extends core.SomeType>(
keyType: Key,
valueType: Value,
params?: string | core.$ZodRecordParams
): ZodRecord<Key, Value>
function z.partialRecord<Key extends core.$ZodRecordKey, Value extends core.SomeType>(
keyType: Key,
valueType: Value,
params?: string | core.$ZodRecordParams
): ZodRecord<Key & core.$partial, Value>
function z.looseRecord<Key extends core.$ZodRecordKey, Value extends core.SomeType>(
keyType: Key,
valueType: Value,
params?: string | core.$ZodRecordParams
): ZodRecord<Key, Value>
| Constructor | Key Validation Behavior | Inferred Type for Key = z.enum(["a", "b"]) |
|---|---|---|
z.record(keyType, valueType) |
Exhaustive: requires all finite literal/enum keys to exist on the object. | Record<"a" | "b", V> ({ a: V; b: V }) |
z.partialRecord(keyType, valueType) |
Non-exhaustive: validates present keys against keyType & valueType without requiring all enum keys. |
Partial<Record<"a" | "b", V>> ({ a?: V; b?: V }) |
z.looseRecord(keyType, valueType) |
Validates keys matching keyType's pattern with valueType, while leaving non-matching keys untouched. |
Record<K, V> & Record<string, unknown> |
import { z } from "zod";
const EnvMap = z.record(z.string(), z.string());
const LocaleOverrides = z.partialRecord(z.enum(["en", "ja", "de"]), z.string());
classZodMap & ZodSet#
Validates native ES6 Map<K, V> and Set<T> instances with built-in .size constraints.
function z.map<Key extends core.SomeType, Value extends core.SomeType>(
keyType: Key,
valueType: Value,
params?: string | core.$ZodMapParams
): ZodMap<Key, Value>
function z.set<Value extends core.SomeType>(
valueType: Value,
params?: string | core.$ZodSetParams
): ZodSet<Value>
| Property / Method | Applicable To | Description |
|---|---|---|
.keyType / .valueType |
ZodMap |
Inspects the key and value schemas. |
.min(n, params?) / .max(n, params?) |
ZodMap, ZodSet |
Validates input.size >= n (too_small) or input.size <= n (too_big). |
.size(n, params?) |
ZodMap, ZodSet |
Validates exact entry count input.size === n. |
.nonempty(params?) |
ZodMap, ZodSet |
Equivalent to .min(1, params). |
import { z } from "zod";
const UniqueIds = z.set(z.uuid()).nonempty().max(100);
classZodUnion & ZodXor#
z.union(options) constructs an inclusive OR union (A | B, succeeding on the first matching branch), while z.xor(options) constructs an exclusive XOR union that succeeds if and only if exactly one branch matches the input.
function z.union<const T extends readonly core.SomeType[]>(options: T, params?: string | core.$ZodUnionParams): ZodUnion<T>
function z.xor<const T extends readonly core.SomeType[]>(options: T, params?: string | core.$ZodXorParams): ZodXor<T>
| Class / Method | Signature | Description |
|---|---|---|
z.union(options, params?) / a.or(b) |
ZodUnion<T> |
Evaluates options in order; returns the output of the first passing schema or emits invalid_union. |
z.xor(options, params?) |
ZodXor<T> |
Evaluates all options; fails with invalid_union (inclusive: false) if 0 or >= 2 options pass. |
.options |
T |
Read-only tuple of constituent member schemas. |
import { z } from "zod";
// Exclusive union: must contain EITHER `id` OR `slug`, not both
const LookupFilter = z.xor([
z.strictObject({ id: z.uuid() }),
z.strictObject({ slug: z.string() }),
]);
classZodDiscriminatedUnion#
Constructs an O(1) lookup union indexed by a shared discriminator property (discriminator: Disc). In Zod v4, z.discriminatedUnion supports nested discriminated unions, pipes, and multi-value literals as discriminator values.
function z.discriminatedUnion<
Types extends readonly [core.$ZodTypeDiscriminable, ...core.$ZodTypeDiscriminable[]],
Disc extends string,
>(
discriminator: Disc,
options: Types,
params?: string | core.$ZodDiscriminatedUnionParams
): ZodDiscriminatedUnion<Types, Disc>
import { z } from "zod";
const WebhookEvent = z.discriminatedUnion("type", [
z.object({ type: z.literal("invoice.paid"), amount: z.int() }),
z.object({ type: z.literal("invoice.failed"), reason: z.string() }),
]);
classZodIntersection#
Constructs a logical AND intersection (A & B) that parses input through both left and right schemas and deeply merges their outputs (z.intersection(a, b) or a.and(b)).
function z.intersection<T extends core.SomeType, U extends core.SomeType>(left: T, right: U): ZodIntersection<T, U>
[!TIP] For combining two
ZodObjectschemas, preferA.extend(B.shape)overz.intersection(A, B)—.extend()produces a flatZodObjectthat supports.keyof(),.pick(),.omit(), and fast JIT object compilation.
classZodOptional & ZodNullable#
Wrapper combinators modify how undefined, null, default values, and parse failures flow into and out of an inner schema T.
Source Code
from drawlib import canvas
from drawlib.diagrams.flow import Decision, FlowDiagram, Process, Start
from drawlib.styles import Styles
from drawlib.text import text
canvas.clear()
canvas.setup(width=184, height=84, dpi=150)
text(
(92, 79.0),
"Zod v4 Wrapper Combinator Execution Flow (Prefault vs Default & Catch)",
style=Styles.DarkBold.patch(text_size=12.5),
)
flow = FlowDiagram(
node_style=Styles.Neutral,
edge_style=Styles.PrimaryBold,
edge_text_style=Styles.DarkBold.patch(text_size=10.0),
)
n_in = flow.add(
Start(
"Raw Input (data)\nparse(data)",
width=28,
height=12,
style=Styles.PrimaryFlat,
text_style=Styles.WhiteBold.patch(text_size=10.0),
),
xy=(16.0, 42.0),
)
d_undef = flow.add(
Decision(
"data ===\nundefined?",
width=28,
height=16,
style=Styles.PrimaryNeutral,
text_style=Styles.DarkBold.patch(text_size=10.0),
),
xy=(54.0, 42.0),
)
n_default = flow.add(
Process(
"ZodDefault / ZodOptional\nShort-circuits inner parse!\nReturns defaultValue / undefined",
width=48,
height=14,
style=Styles.SecondaryNeutral,
text_style=Styles.DarkBold.patch(text_size=10.0),
),
xy=(104.0, 62.0),
)
n_prefault = flow.add(
Process(
"ZodPrefault(initVal)\nSubstitutes data = initVal\nand runs innerType!",
width=44,
height=14,
style=Styles.PrimaryNeutral,
text_style=Styles.DarkBold.patch(text_size=10.0),
),
xy=(54.0, 12.0),
)
n_inner = flow.add(
Process(
"Inner Schema (innerType)\nValidates & Transforms\n(or ZodNullable if null)",
width=48,
height=14,
style=Styles.Neutral,
text_style=Styles.DarkBold.patch(text_size=10.0),
),
xy=(104.0, 32.0),
)
n_out = flow.add(
Process(
"ZodCatch / ZodReadonly\nOn error -> catchValue(ctx)\nOn ok -> Object.freeze / $brand",
width=44,
height=16,
style=Styles.AccentFlat,
text_style=Styles.WhiteBold.patch(text_size=10.0),
),
xy=(156.0, 46.0),
)
flow.connect(n_in, d_undef)
flow.connect(d_undef, n_default, label="Yes (default)", start_side="top", end_side="left")
flow.connect(d_undef, n_prefault, label="Yes (prefault)", start_side="bottom", end_side="top")
flow.connect(d_undef, n_inner, label="No", start_side="right", end_side="left")
flow.connect(n_prefault, n_inner, start_side="right", end_side="bottom")
flow.connect(n_default, n_out, start_side="right", end_side="top")
flow.connect(n_inner, n_out, start_side="right", end_side="bottom")
flow.draw(xy=(2.0, 2.0))
canvas.save()
function z.optional<T extends core.SomeType>(innerType: T): ZodOptional<T>
function z.exactOptional<T extends core.SomeType>(innerType: T): ZodExactOptional<T>
function z.nullable<T extends core.SomeType>(innerType: T): ZodNullable<T>
function z.nullish<T extends core.SomeType>(innerType: T): ZodOptional<ZodNullable<T>>
function z.nonoptional<T extends core.SomeType>(innerType: T, params?: string | core.$ZodNonOptionalParams): ZodNonOptional<T>
| Wrapper Class / Function | z.input<S> |
z.output<S> |
Behavior & .unwrap() |
|---|---|---|---|
ZodOptional<T> (.optional()) |
Input<T> | undefined |
Output<T> | undefined |
Short-circuits and returns undefined when input is undefined. Unwraps via .unwrap(). |
ZodExactOptional<T> (.exactOptional()) |
Input<T> (key omittable) |
Output<T> (key omittable) |
Inside ZodObject, allows omitting the property key entirely, but rejects { key: undefined }. |
ZodNullable<T> (.nullable()) |
Input<T> | null |
Output<T> | null |
Short-circuits and returns null when input is null. Unwraps via .unwrap(). |
z.nullish(schema) (.nullish()) |
Input<T> | null | undefined |
Output<T> | null | undefined |
Equivalent to z.optional(z.nullable(schema)). |
ZodNonOptional<T> (.nonoptional()) |
Exclude<Input<T>, undefined> |
Exclude<Output<T>, undefined> |
Rejects undefined even if innerType is ZodOptional or ZodUnknown. |
classZodDefault & ZodPrefault#
Zod v4 provides both output-side short-circuit defaults (ZodDefault) and input-side pre-parse defaults (ZodPrefault):
function z._default<T extends core.SomeType>(
innerType: T,
defaultValue: util.NoUndefined<core.output<T>> | (() => util.NoUndefined<core.output<T>>)
): ZodDefault<T>
function z.prefault<T extends core.SomeType>(
innerType: T,
defaultValue: core.input<T> | (() => core.input<T>)
): ZodPrefault<T>
| Feature | schema.default(valueOrFn) (ZodDefault<T>) |
schema.prefault(valueOrFn) (ZodPrefault<T>) |
|---|---|---|
| Default Value Type | z.output<T> (post-transform output type) |
z.input<T> (pre-transform raw input type) |
Execution on undefined |
Short-circuits innerType: returns defaultValue immediately without running checks or transforms. |
Feeds defaultValue into innerType: executes all inner checks, refinements, and transforms. |
| Mutable Value Safety | Shallow-clones object/array defaults on each parse or invokes () => defaultValue. |
Shallow-clones object/array defaults on each parse or invokes () => defaultValue. |
import { z } from "zod";
const TrimmedWithDefault = z.string().trim().default("guest");
TrimmedWithDefault.parse(undefined); // => "guest" (short-circuited)
const TrimmedWithPrefault = z.string().trim().prefault(" guest ");
TrimmedWithPrefault.parse(undefined); // => "guest" (parsed & trimmed through innerType!)
classZodCatch & ZodReadonly#
Error-recovery (ZodCatch), boolean success coercion (ZodSuccess), deep/shallow immutability (ZodReadonly), and nominal type branding ($ZodBranded):
function z.catch<T extends core.SomeType>(
innerType: T,
catchValue: core.output<T> | ((ctx: core.$ZodCatchCtx) => core.output<T>)
): ZodCatch<T>
function z.readonly<T extends core.SomeType>(innerType: T): ZodReadonly<T>
function z.success<T extends core.SomeType>(innerType: T): ZodSuccess<T>
| Combinator | Return Type | Runtime & Type Behavior |
|---|---|---|
schema.catch(valOrFn) |
ZodCatch<this> |
If innerType fails validation, suppresses the error and returns valOrFn({ error, input, issues, value }). |
schema.readonly() |
ZodReadonly<this> |
Calls Object.freeze(output) at runtime and wraps output type in Readonly<Output<T>> (readonly T[], ReadonlyMap, ReadonlySet). |
z.success(schema) |
ZodSuccess<T> |
Parses input through schema and returns boolean (true if valid, false if invalid). |
schema.brand<Name, Dir?>() |
core.$ZodBranded<this, Name, Dir> |
Compile-time zero-cost nominal brand (Dir = "out" default, "in", or "inout"). |
import { z } from "zod";
const SafeRetryCount = z.int().min(0).catch((ctx) => {
console.warn("Invalid retry count, falling back to 3:", ctx.error.issues);
return 3;
});
const UserId = z.uuid().brand<"UserId">();
type UserId = z.infer<typeof UserId>; // string & z.$brand<"UserId">
functionz.lazy & z.custom#
Recursive schemas (z.lazy), arbitrary TypeScript type predicates (z.custom), class instance checks (z.instanceof), and recursive JSON value schemas (z.json).
[!TIP] Native Recursive Objects in Zod v4: You no longer need
z.lazy()for most recursive objects and arrays! Using a getter property insidez.object({ get subcategories() { return z.array(Category); } })preserves full static type inference without manualz.ZodTypeannotations. Usez.lazy(() => ...)when defining top-level mutually recursive unions or aliases.
function z.lazy<T extends core.SomeType>(getter: () => T): ZodLazy<T>
function z.custom<O>(fn?: (data: unknown) => unknown, params?: string | core.$ZodCustomParams): ZodCustom<O, O>
function z.instanceof<T extends typeof util.Class>(cls: T, params?: ZodInstanceOfParams): ZodInstanceOf<InstanceType<T>>
function z.json(params?: string | core.$ZodCustomParams): ZodJSONSchema
| Constructor | Key Methods / Parameters | Description |
|---|---|---|
z.lazy(getter) |
.unwrap(): T |
Evaluates getter() lazily on first parse or JSON Schema traversal. |
z.custom<T>(predicate?, params?) |
fn: (data: unknown) => boolean | Promise<boolean> |
Creates a ZodCustom<T, T> schema validated by predicate. Defaults to abort: true. |
z.instanceof(Class, params?) |
.properties(shape, params?) |
Validates data instanceof Class, and optionally validates instance properties via .properties({ ... }). |
z.json(params?) |
ZodJSONSchema (util.JSONType) |
Built-in recursive schema matching any valid JSON value (string | number | boolean | null | JSONType[] | { [k: string]: JSONType }). |
import { z } from "zod";
// Native getter recursion in Zod v4 (no z.lazy boilerplate needed)
const Category = z.object({
name: z.string(),
get children() {
return z.array(Category);
},
});
classZodFunction & ZodPromise#
z.function() constructs a runtime-validated function wrapper (ZodFunction<Args, Returns>) that validates incoming arguments and return values whenever the wrapped function is invoked. z.promise(schema) validates that an input is a Promise and parses its resolved value.
function z.function<In extends core.$ZodFunctionIn, Out extends core.$ZodType>(params?: {
input?: In | ReadonlyArray<core.$ZodType>;
output?: Out;
}): ZodFunction<In, Out>
function z.promise<T extends core.SomeType>(innerType: T): ZodPromise<T>
ZodFunction Method |
Signature | Description |
|---|---|---|
.input(tupleOrArray, rest?) |
(args: TupleItems | ZodTuple) => ZodFunction |
Configures or replaces the argument tuple schema. |
.output(returnSchema) |
(output: $ZodType) => ZodFunction |
Configures or replaces the return value schema. |
.implement(fn) |
(fn: InnerFn) => OuterFn |
Wraps synchronous or asynchronous fn so both arguments and return values are validated on every call. |
.implementAsync(fn) |
(fn: AsyncInnerFn) => AsyncOuterFn |
Explicitly wraps an async implementation returning a Promise. |
import { z } from "zod";
const ComputeTax = z.function({
input: [z.number().nonnegative(), z.number().min(0).max(1)],
output: z.number().nonnegative(),
});
const calc = ComputeTax.implement((amount, rate) => amount * rate);
calc(100, 0.08); // => 8