zaggino/z-schema

writing-json-schemas

Authors JSON Schema definitions for use with z-schema validation.

First seen Feb 27, 2026

Installation

$ npx skills add zaggino/z-schema --skill writing-json-schemas

Summary

  • 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.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from zaggino/z-schema.

npx skills add zaggino/z-schema

Browse all from zaggino/z-schema

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 350
License LICENSE
Default branch main
Open issues 0
Status Active

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 7,955 B
  • docs SUMMARY.md 482 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 233 installs

SKILL.md

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: false or use unevaluatedProperties: false to reject them.
  • Using additionalProperties: false with allOf: This blocks properties from sibling schemas. Use unevaluatedProperties: false at the top level instead (draft-2019-09+).
  • Array items in draft-2020-12: Use prefixItems for tuple validation. items now means "schema for remaining items".
  • Missing $schema: Without it, z-schema uses its configured default draft. Include $schema for explicit draft targeting.
  • definitions vs $defs: Both work, but $defs is the canonical form in draft-2019-09+. Use it consistently.