Capítulo 33 de 61
z.custom<T>() lets you validate any TypeScript type with a hand-written predicate when no built-in schema fits; .apply() lets you inject reusable, external logic into a schema's method chain.
z.custom<T>(validatorFn): validates T-typed values using an arbitrary predicate function. Prefer z.instanceof() for class instances and z.templateLiteral() for template literal types instead — z.custom() is the fallback for everything else (e.g. third-party library types like Decimal).z.custom<T>() called with no arguments accepts any value and performs zero runtime checks — a real footgun if used carelessly..refine() accepts) to customize the failure message..apply(fn, ...args): calls fn(schema, ...args) inline in a method chain, so reusable schema-building logic (e.g. "apply our standard number range checks") can be shared across schemas without breaking the fluent chain.import { Decimal } from "decimal.js";
const decimalSchema = z.custom<Decimal>((val) => Decimal.isDecimal(val));
decimalSchema.parse(new Decimal("1.5")); // passes
decimalSchema.parse("1.5"); // throws
// apply: inject reusable logic into a chain
function setCommonNumberChecks<T extends z.ZodNumber>(schema: T) {
return schema.min(0).max(100);
}
const schema = z.number().apply(setCommonNumberChecks).nullable();
schema.parse(0); // ok
schema.parse(101); // throws
schema.parse(null); // ok
// apply passes extra arguments through to the function
function withDefault<T extends z.ZodType>(schema: T, value: z.output<T>) {
return schema.nullish().transform((val) => val ?? value);
}
const withFallback = z.string().apply(withDefault, "anonymous");
withFallback.parse(undefined); // => "anonymous"
.apply().z.custom<T>() with no validator "just to get the type": it performs zero validation — any value passes — which defeats the point of a runtime schema and can silently let invalid data through.z.custom() for class instances or template literals: z.instanceof() and z.templateLiteral() are the purpose-built, more precise tools for those cases.z.custom<T>() is the last resort for types nothing else covers — always supply a real validator function..apply() keeps shared validation logic (like standard range checks) reusable and chainable, instead of copy-pasted across schemas..apply(fn, ...args) pass straight through to fn, enabling parameterized reusable schema builders.