writing-json-schemas
Writing JSON Schemas for z-schema
Write correct, idiomatic JSON Schemas validated by z-schema. Default target: draft-2020-12.
Schema template
Start every schema with a $schema declaration and type:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {},
"required": [],
"additionalProperties": false
}
Set additionalProperties: false explicitly when extra properties should be rejected — z-schema allows them by default.
Object schemas
Basic object with required fields
{
"type": "object",
"properties": {
"name": { "type": "string", "minLength": 1 },
"email": { "type": "string", "format": "email" },
"age": { "type": "integer", "minimum": 0, "maximum": 150 }
},
"required": ["name", "email"],
"additionalProperties": false
}
Nested objects
{
"type": "object",
"properties": {
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"zip": { "type": "string", "pattern": "^\\d{5}(-\\d{4})?$" }
},
"required": ["street", "city"]
}
}
}
Dynamic property names
Use patternProperties to validate property keys by regex:
{
"type": "object",
"patternProperties": {
"^x-": { "type": "string" }
},
"additionalProperties": false
}
Use propertyNames (draft-06+) to constrain all property key strings:
{
"type": "object",
"propertyNames": { "pattern": "^[a-z_]+$" }
}
Array schemas
Uniform array
{
"type": "array",
"items": { "type": "string" },
"minItems": 1,
"uniqueItems": true
}
Tuple validation (draft-2020-12)
Use prefixItems for positional types, items for remaining elements:
{
"type": "array",
"prefixItems": [{ "type": "string" }, { "type": "integer" }],
"items": false
}
items: false rejects extra elements beyond the tuple positions.
Contains (draft-06+)
Require at least one matching item:
{
"type": "array",
"contains": { "type": "string", "const": "admin" }
}
With count constraints (draft-2019-09+):
{
"type": "array",
"contains": { "type": "integer", "minimum": 10 },
"minContains": 2,
"maxContains": 5
}
String constraints
{
"type": "string",
"minLength": 1,
"maxLength": 255,
"pattern": "^[A-Za-z0-9_]+$"
}
Format validation
z-schema has built-in format validators: date, date-time, time, email, idn-email, hostname, idn-hostname, ipv4, ipv6, uri, uri-reference, uri-template, iri, iri-reference, json-pointer, relative-json-pointer, regex, duration, uuid.
{ "type": "string", "format": "date-time" }
Format assertions are always enforced by default (formatAssertions: null). For vocabulary-aware behavior in draft-2020-12, set formatAssertions: true on the validator.
Numeric constraints
{
"type": "number",
"minimum": 0,
"maximum": 100,
"multipleOf": 0.01
}
Use exclusiveMinimum / exclusiveMaximum for strict bounds:
{ "type": "integer", "exclusiveMinimum": 0, "exclusiveMaximum": 100 }
Combinators
anyOf — match at least one
{
"anyOf": [{ "type": "string" }, { "type": "number" }]
}
oneOf — match exactly one
{
"oneOf": [
{ "type": "string", "maxLength": 5 },
{ "type": "string", "minLength": 10 }
]
}
allOf — match all
Use for schema composition. Combine base schemas with refinements:
{
"allOf": [{ "$ref": "#/$defs/base" }, { "properties": { "extra": { "type": "string" } } }]
}
not — must not match
{ "not": { "type": "null" } }
if / then / else (draft-07+)
Conditional validation — prefer over complex oneOf when the logic is "if X then require Y":
{
"type": "object",
"properties": {
"type": { "type": "string", "enum": ["personal", "business"] },
"company": { "type": "string" }
},
"if": { "properties": { "type": { "const": "business" } } },
"then": { "required": ["company"] },
"else": {}
}
When to use which combinator
| Scenario | Use |
|---|---|
| Value can be multiple types | anyOf |
| Exactly one variant must match | oneOf |
| Compose inherited schemas | allOf |
| "if condition then require fields" | if/then/else |
| Exclude a specific shape | not |
Prefer if/then/else over oneOf when the condition is a single discriminator field — it produces clearer error messages.
Schema reuse with $ref and $defs
Local definitions
{
"$defs": {
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" }
},
"required": ["street", "city"]
}
},
"type": "object",
"properties": {
"home": { "$ref": "#/$defs/address" },
"work": { "$ref": "#/$defs/address" }
}
}
Cross-schema references
Compile an array of schemas and reference by ID:
import ZSchema from 'z-schema';
const schemas = [
{
$id: 'address',
type: 'object',
properties: { city: { type: 'string' } },
required: ['city'],
},
{
$id: 'person',
type: 'object',
properties: {
name: { type: 'string' },
home: { $ref: 'address' },
},
required: ['name'],
},
];
const validator = ZSchema.create();
validator.validateSchema(schemas);
validator.validate({ name: 'Alice', home: { city: 'Paris' } }, 'person');
Strict schemas with unevaluatedProperties (draft-2019-09+)
When combining schemas with allOf, additionalProperties: false in a sub-schema blocks properties defined in sibling schemas. Use unevaluatedProperties instead — it tracks all properties evaluated across applicators:
{
"allOf": [
{
"type": "object",
"properties": { "name": { "type": "string" } },
"required": ["name"]
},
{
"type": "object",
"properties": { "age": { "type": "integer" } }
}
],
"unevaluatedProperties": false
}
This accepts { "name": "Alice", "age": 30 } but rejects { "name": "Alice", "age": 30, "extra": true }.
Validating the schema itself
Always validate schemas at startup:
const validator = ZSchema.create();
try {
validator.validateSchema(schema);
} catch (err) {
console.log('Schema errors:', err.details);
}
Common mistakes
- Forgetting
additionalProperties: By default, extra properties are allowed. SetadditionalProperties: falseor useunevaluatedProperties: falseto reject them. - Using
additionalProperties: falsewithallOf: This blocks properties from sibling schemas. UseunevaluatedProperties: falseat the top level instead (draft-2019-09+). - Array
itemsin draft-2020-12: UseprefixItemsfor tuple validation.itemsnow means "schema for remaining items". - Missing
$schema: Without it, z-schema uses its configured default draft. Include$schemafor explicit draft targeting. definitionsvs$defs: Both work, but$defsis the canonical form in draft-2019-09+. Use it consistently.
More from zaggino/z-schema
validating-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.
65custom-format-validators
Registers and manages custom format validators in z-schema. Use when the user needs to add custom format validation, create sync or async format validators, register formats globally or per instance, validate emails or dates or phone numbers or custom business rules with format, configure formatAssertions for vocabulary-aware behavior, use customFormats option, list registered formats, handle async format timeouts, or understand how format validation differs across JSON Schema drafts.
64contributing-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