Validate external-library types
A third-party library exposes a branded or structural type that Zod has no dedicated schema for.
Zod · Function
z.custom() creates a Zod schema for a TypeScript type that lacks a built-in Zod schema. Supply a predicate for runtime validation and an optional custom error message; without a predicate, it accepts any value.
z.custom() creates a Zod schema for a TypeScript type that is not covered by a built-in schema. An optional predicate decides whether an input is accepted, and an optional second argument customizes the validation error; omitting the predicate performs no validation.
z.custom<...>((val) => ..., "custom error message")| Name | Type | Description |
|---|---|---|
T | TypeScript type | Optional TypeScript generic describing the accepted value type; the documentation represents it as ... in the signature. |
check | (val) => boolean | Optional validation predicate receiving the value and returning whether it is valid. |
message | string | Optional custom validation error message. |
A Zod schema for the specified TypeScript type.
import { z } from "zod";
const Pixel = z.custom<`${number}px`>(
(value) => typeof value === "string" && /^\d+px$/.test(value),
"Expected a pixel value"
);
console.log(Pixel.parse("42px"));
console.log(Pixel.safeParse("42vw").success);The predicate accepts strings consisting of digits followed by px; the second argument supplies the custom validation message.
A third-party library exposes a branded or structural type that Zod has no dedicated schema for.
A domain value follows a custom string or structural rule not covered by built-in schemas.
A TypeScript type requires a schema for parsing or inference but has no direct Zod counterpart.
z.custom() defines a schema for a TypeScript type that Zod does not otherwise cover. Its predicate receives the input and must determine whether validation succeeds.
Follow-up: When should a different API be preferred? Use z.instanceof() for class instances and z.templateLiteral() for template literal types.
The generic parameter describes the TypeScript type inferred from the schema, while the predicate supplies runtime validation. For example, a predicate can check that a value matches a string pattern.
Follow-up: What happens if the predicate is omitted? The schema performs no runtime validation and accepts any value.
A second argument can provide a custom error message for failed validation. This makes failures identify the intended domain-specific constraint instead of relying only on a generic message.
Follow-up: How could class-instance validation be made more idiomatic? Replace z.custom() with z.instanceof() when the target is a class instance.
z.instanceof()z.templateLiteral()z.refine()z.infer