Skill v1.0.1
currentAutomated scan100/1004 files
version: "1.0.1" name: writing-json-schemas description: 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.
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. Set
additionalProperties: falseor useunevaluatedProperties: falseto reject them. - Using `additionalProperties: false` with `allOf`: This blocks properties from sibling schemas. Use
unevaluatedProperties: falseat the top level instead (draft-2019-09+). - Array `items` in draft-2020-12: Use
prefixItemsfor tuple validation.itemsnow means "schema for remaining items". - Missing `$schema`: Without it, z-schema uses its configured default draft. Include
$schemafor explicit draft targeting. - `definitions` vs `$defs`: Both work, but
$defsis the canonical form in draft-2019-09+. Use it consistently.