z-schema
z-schema is a TypeScript-based JSON Schema validator for Node.js and browsers, plus a small command-line interface. Version 12 supports draft-04, draft-06, draft-07, draft-2019-09, and draft-2020-12, which is now the default. A factory creates synchronous or asynchronous validators in throwing or result-object modes. It also compiles linked schemas, resolves registered references, supports custom formats and validation hooks, and reports structured errors with codes, paths, keywords, nested details, and schema locations.
z-schema 12 is no longer the sleepy draft-04 validator many developers remember; it is active, typed, and current through draft-2020-12. Choose it for multi-draft coverage and rich modes, but pin the draft and budget for its Node 22 floor and major-version migration history.
Use it if
- You need one validator to handle schemas spanning draft-04 through draft-2020-12
- You want selectable throw, safe-result, async, and async-safe validation modes from the same package
- You need structured multi-error output, custom formats, remote-reference hooks, or validation of a schema collection
- Your runtime is Node.js 22 or newer, or you control a supported modern browser bundle
- You run Node 20 or older: the 12.4.1 package declares Node.js 22 as its minimum engine
- You want a low-change validator API: v7 rewrote the project in TypeScript and ESM, v9 replaced new ZSchema() with ZSchema.create(), and v10 through v12 changed the default schema draft in successive majors
- You assume strictMode means ordinary JSON Schema strictness: it also requires maxLength on strings, properties on objects, items on arrays, and explicit additional-properties decisions, so existing schemas can fail compilation for house-style reasons
- You expect remote $ref values to be fetched safely without policy code: automatic downloading was removed, schema readers are application callbacks, and global registered references are shared across instances with last-write-wins behavior
- You mainly need maximum JSON validation speed with standalone generated validator code and a larger plugin ecosystem; Ajv is the better-established fit for that workflow
Setup reality
Install z-schema and use ZSchema.create(), not new ZSchema(). Version 12.4.1 requires Node.js 22 or later and publishes ESM, CommonJS, UMD, CLI, and generated TypeScript declarations. There are no native builds or credentials, but punycode, validator, and safe-regex2 are runtime dependencies; commander is optional and supports the CLI. Choose the validator mode at creation time. The default synchronous validator returns true or throws ValidateError. safe: true returns { valid, err }, async: true changes validate to a promise and is required for asynchronous format checks, and combining both produces a promise that resolves with the result object. Pick a schema version deliberately. Schemas without an explicit $schema use draft-2020-12 in v12, while older z-schema majors used different defaults; tuple items, IDs, dependencies, and format behavior can therefore change on upgrade. The default formatAssertions value keeps legacy assertion behavior, while vocabulary-aware modern-draft behavior requires an option. Pre-validate linked schemas at startup with validateSchema so bad references fail before traffic arrives. Remote schemas are not ordinary network fetches: register them on an instance or provide a carefully restricted schema reader. Global references and global custom formats cross validator-instance boundaries, which can cause test or tenant contamination. Async tasks default to a 2,000 ms timeout and are capped at 60,000 ms. Full error collection is the default; breakOnFirstError can reduce work when callers need only pass or fail. The browser UMD file exists, but server code, filesystem readers, and CLI examples do not transfer to browser deployments. strictMode bundles many opinionated checks, so enable individual options if you do not want its entire schema policy.
Patterns
Validate without exceptionsvalidate-with-safe-result
import ZSchema from 'z-schema';
const validator = ZSchema.create({ safe: true });
const result = validator.validate(data, schema);
if (!result.valid) {
console.error(result.err?.details);
}safe is a factory option that changes validate's return type. A default validator instead returns true or throws ValidateError.
Handle structured errors in throw modecatch-validation-error
const validator = ZSchema.create();
try {
validator.validate(data, schema);
} catch (error) {
if (error.name === 'ValidateError') {
for (const detail of error.details) {
console.error(detail.path, detail.keyword, detail.message);
}
} else {
throw error;
}
}Do not parse the summary message. details provides stable codes, keyword names, data paths, schema paths, and nested errors.
Pin draft-07 behavior during migrationpin-schema-draft
const validator = ZSchema.create({ version: 'draft-07' });
validator.validate(data, {
$schema: 'http://json-schema.org/draft-07/schema#',
type: 'object',
});Version 12 defaults schemas without an explicit declaration to draft-2020-12. Pinning both configuration and $schema makes intent reviewable.
Run an asynchronous format check safelyvalidate-async-format
const validator = ZSchema.create({
async: true,
safe: true,
asyncTimeout: 1500,
});
validator.registerFormat('user-exists', async (id) => {
return typeof id === 'number' && await users.exists(id);
});
const result = await validator.validate(data, schema);Async format functions require async: true. The timeout applies to async work and is silently capped at 60,000 ms.
Compile schemas with cross-references at startupcompile-linked-schemas
const schemas = [
{ $id: 'person', type: 'object', properties: { name: { type: 'string' } }, required: ['name'] },
{ $id: 'team', type: 'object', properties: { lead: { $ref: 'person' } } },
];
const validator = ZSchema.create();
validator.validateSchema(schemas);
validator.validate({ lead: { name: 'Ada' } }, schemas[1]);Compile before serving requests so invalid schemas and unresolved links fail during startup rather than on the first matching payload.
Register a remote reference on one validatorregister-instance-reference
const validator = ZSchema.create();
validator.setRemoteReference(
'https://schemas.example/address.json',
addressSchema
);
validator.validate(data, rootSchema);Instance references take precedence and avoid the process-wide last-write-wins cache used by ZSchema.setRemoteReference.
Load references through an allowlisted readerrestrict-schema-reader
ZSchema.setSchemaReader((uri) => {
const schema = approvedSchemas.get(uri);
if (!schema) throw new Error(`Schema URI not allowed: ${uri}`);
return schema;
});The package does not safely download arbitrary remote schemas for you. Do not map untrusted URIs directly to filesystem paths or unrestricted HTTP requests.
Add an instance-scoped formatregister-custom-format
const validator = ZSchema.create();
validator.registerFormat('order-id', (value) => {
return typeof value === 'string' && /^ORD-[0-9]{8}$/.test(value);
});
validator.validate('ORD-00123456', { type: 'string', format: 'order-id' });Prefer instance registration when tests or tenants use different rules. Global ZSchema.registerFormat affects every instance in the process.
Short-circuit invalid production payloadsstop-at-first-error
const validator = ZSchema.create({
safe: true,
breakOnFirstError: true,
});
const { valid, err } = validator.validate(request.body, schema);The default collects all errors. Short-circuit only when the caller does not need a complete list for a form or diagnostics.
Return machine-friendly path segmentsreport-array-paths
const validator = ZSchema.create({
safe: true,
reportPathAsArray: true,
});
const result = validator.validate(data, schema);
const path = result.err?.details[0]?.path;
// for example: ['users', 0, 'email']Without reportPathAsArray, paths use JSON Pointer-like strings such as #/users/0/email. Choose one representation at your API boundary.
Validate against one schema branchvalidate-subschema
validator.validate(address, rootSchema, {
schemaPath: '#/properties/address',
});schemaPath targets a schema location, not a path into the data. Validate the corresponding data value yourself.
Check a schema and payload from the CLIvalidate-from-cli
npx z-schema schema.json data.json
npx z-schema --strictMode schema.json data.jsonstrictMode enables a bundle of opinionated schema checks, including required maxLength, properties, items, and additional-property declarations. It is not merely stricter value validation.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| ajv | npm | You want the dominant high-performance JSON Schema compiler, standalone code generation, and a broad plugin ecosystem |
| @hyperjump/json-schema | npm | You prioritize modern JSON Schema dialect architecture and spec-focused evaluation APIs |
| jsonschema | npm | You need a familiar older validation API and your schemas fit its supported drafts |
| djv | npm | You have a constrained hot path and want a compact dynamic validator for a narrower feature set |