custom-format-validators
Custom Format Validators in z-schema
JSON Schema format constrains string (or other) values beyond basic type checking. z-schema includes built-in validators and supports registering custom ones — both sync and async.
Built-in formats
z-schema ships with validators for all standard JSON Schema formats:
| Format | Validates |
|---|---|
date |
RFC 3339 full-date (2024-01-15) |
date-time |
RFC 3339 date-time (2024-01-15T09:30:00Z) |
time |
RFC 3339 time (09:30:00Z) |
email |
RFC 5321 email address |
idn-email |
Internationalized email |
hostname |
RFC 1123 hostname |
idn-hostname |
Internationalized hostname |
ipv4 |
IPv4 address |
ipv6 |
IPv6 address |
uri |
RFC 3986 URI |
uri-reference |
URI or relative reference |
uri-template |
RFC 6570 URI template |
iri |
Internationalized URI |
iri-reference |
Internationalized URI reference |
json-pointer |
RFC 6901 JSON Pointer |
relative-json-pointer |
Relative JSON Pointer |
regex |
ECMA-262 regex |
duration |
ISO 8601 duration |
uuid |
RFC 4122 UUID |
Registering a sync format
A format validator is a function (input: unknown) => boolean. Return true if valid, false if invalid. Return true for non-applicable types (e.g., when input is not a string) — z-schema calls format validators for any value type.
Global registration (shared across all instances)
import ZSchema from 'z-schema';
ZSchema.registerFormat('postal-code', (value) => {
if (typeof value !== 'string') return true; // skip non-strings
return /^\d{5}(-\d{4})?$/.test(value);
});
Instance-scoped registration
const validator = ZSchema.create();
validator.registerFormat('postal-code', (value) => {
if (typeof value !== 'string') return true;
return /^\d{5}(-\d{4})?$/.test(value);
});
Instance formats override global formats with the same name.
Via options at creation time
const validator = ZSchema.create({
customFormats: {
'postal-code': (value) => typeof value === 'string' && /^\d{5}(-\d{4})?$/.test(value),
'always-valid': () => true,
'disable-email': null, // disable the built-in email validator
},
});
Pass null to disable a built-in or globally registered format.
Registering an async format
Return Promise<boolean>. The validator must be created with { async: true }.
const validator = ZSchema.create({ async: true });
validator.registerFormat('user-exists', async (value) => {
if (typeof value !== 'number') return true;
const user = await db.findUser(value);
return user != null;
});
// Validate (returns Promise)
try {
await validator.validate(data, schema);
} catch (err) {
console.log(err.details);
}
Timeout
Async format validators time out after asyncTimeout milliseconds (default: 2000). Increase for slow operations:
const validator = ZSchema.create({
async: true,
asyncTimeout: 10000, // 10 seconds
});
Timed-out validators produce an ASYNC_TIMEOUT error.
Format assertion behavior across drafts
The JSON Schema spec changed how format works in newer drafts:
| Draft | Default behavior | With formatAssertions: true |
|---|---|---|
| draft-04/06/07 | Always asserts (fails on mismatch) | Always asserts |
| draft-2019-09 | Always asserts (z-schema default) | Annotation-only unless vocabulary enabled |
| draft-2020-12 | Always asserts (z-schema default) | Annotation-only unless vocabulary enabled |
z-schema defaults to formatAssertions: null (legacy — always assert). To respect the spec's vocabulary-aware behavior for modern drafts:
const validator = ZSchema.create({ formatAssertions: true });
To disable all format assertions (annotation-only):
const validator = ZSchema.create({ formatAssertions: false });
Unknown formats
By default, z-schema reports UNKNOWN_FORMAT for unrecognized format names in draft-04/06/07. Modern drafts (2019-09, 2020-12) always silently ignore unknown formats.
To suppress unknown format errors in older drafts:
const validator = ZSchema.create({ ignoreUnknownFormats: true });
Unregistering a format
// Global
ZSchema.unregisterFormat('postal-code');
// Instance
validator.unregisterFormat('postal-code');
Listing formats
// List globally registered custom formats
const customFormats = ZSchema.getRegisteredFormats();
// List all supported formats (built-in + custom) on an instance
const allFormats = validator.getSupportedFormats();
// Check if a specific format is supported
const supported = validator.isFormatSupported('postal-code');
Real-world patterns
Phone number validation
ZSchema.registerFormat('phone', (value) => {
if (typeof value !== 'string') return true;
return /^\+?[1-9]\d{1,14}$/.test(value); // E.164 format
});
ISO 8601 date (strict)
ZSchema.registerFormat('iso-date', (value) => {
if (typeof value !== 'string') return true;
const d = new Date(value);
return !isNaN(d.getTime()) && value === d.toISOString().split('T')[0];
});
Business rule: value from external list
const validator = ZSchema.create({ async: true });
validator.registerFormat('valid-country', async (value) => {
if (typeof value !== 'string') return true;
const countries = await fetchValidCountries();
return countries.includes(value.toUpperCase());
});
Side-effect: prefill defaults
Format validators can mutate objects (use with caution):
ZSchema.registerFormat('fill-defaults', (obj) => {
if (typeof obj === 'object' && obj !== null) {
(obj as Record<string, unknown>).createdAt ??= new Date().toISOString();
}
return true;
});
Schema usage
{
"type": "object",
"properties": {
"phone": { "type": "string", "format": "phone" },
"country": { "type": "string", "format": "valid-country" },
"zipCode": { "type": "string", "format": "postal-code" }
}
}
More from zaggino/z-schema
writing-json-schemas
Authors JSON Schema definitions for use with z-schema validation. Use when the user needs to write a JSON Schema, define a schema for an API payload, create schemas for form validation, structure schemas with $ref and $defs, choose between oneOf/anyOf/if-then-else, design object schemas with required and additionalProperties, validate arrays with items or prefixItems, add format constraints, organize schemas for reuse, or write draft-2020-12 schemas.
87validating-json-data
Validates JSON data against JSON Schema using the z-schema library. Use when the user needs to validate JSON, check data against a schema, handle validation errors, use custom format validators, work with JSON Schema drafts 04 through 2020-12, set up z-schema in a project, compile schemas with cross-references, resolve remote $ref, configure validation options, or inspect error details. Covers sync/async modes, safe error handling, schema pre-compilation, remote references, TypeScript types, and browser/UMD usage.
74skill-creator
Create, improve, and test skills for the z-schema JSON Schema validator library. Use this skill whenever the user wants to create a new skill from scratch, turn a workflow into a reusable skill, update or refine an existing skill, write test cases for a skill, or organize reference material for a skill. Also use when someone mentions "skill", "SKILL.md", or wants to document a z-schema workflow for reuse by humans or AI agents.
65migrating-json-schemas
Migrates JSON Schemas between draft versions for use with z-schema. Use when the user wants to upgrade schemas from draft-04 to draft-2020-12, convert between draft formats, update deprecated keywords, replace id with $id, convert definitions to $defs, migrate items to prefixItems, replace dependencies with dependentRequired or dependentSchemas, adopt unevaluatedProperties or unevaluatedItems, or adapt schemas to newer JSON Schema features.
65contributing-to-z-schema
Guides contributors through the z-schema codebase, PR workflow, and common development tasks. Use when the user wants to contribute to z-schema, add a new feature or keyword, add an error code, add a format validator, modify options, write tests, run the test suite, fix a failing test, understand the validation pipeline, navigate the source code architecture, or submit a pull request. Also use when someone mentions contributing, PRs, the z-schema source code, or the JSON Schema Test Suite integration.
64handling-validation-errors
Inspects, filters, and maps z-schema validation errors for application use. Use when the user needs to handle validation errors, walk nested inner errors from anyOf/oneOf/not combinators, map error codes to user-friendly messages, filter errors with includeErrors or excludeErrors, build form-field error mappers, use reportPathAsArray, interpret SchemaErrorDetail fields like code/path/keyword/inner, or debug why validation failed.
63