Anatomy of a Schema

Every JSON Schema document is itself a JSON object. The $schema keyword declares the dialect version. Everything else describes constraints on the instance being validated:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://api.example.com/schemas/user",
  "title": "User",
  "description": "A registered user account",
  "type": "object",
  "properties": {
    "id":    { "type": "string" },
    "name":  { "type": "string", "minLength": 1, "maxLength": 100 },
    "email": { "type": "string", "format": "email" },
    "age":   { "type": "integer", "minimum": 0, "maximum": 150 },
    "role":  { "type": "string", "enum": ["admin", "editor", "viewer"] }
  },
  "required": ["id", "name", "email"],
  "additionalProperties": false
}

The Six Primitive Types

Type keywordJSON values it matchesCommon keywords
stringAny JSON stringminLength, maxLength, pattern, format
numberAny JSON number (int or float)minimum, maximum, exclusiveMinimum, multipleOf
integerNumbers with no fractional partSame as number
booleantrue or false only
nullJSON null only
objectJSON objects {}properties, required, additionalProperties, minProperties
arrayJSON arrays []items, prefixItems, minItems, maxItems, uniqueItems

Schema Composition Keywords

Four Boolean logic operators let you combine schemas:

// allOf: must match ALL schemas (intersection)
"allOf": [
  { "type": "object" },
  { "required": ["id"] }
]

// anyOf: must match AT LEAST ONE schema (union)
"anyOf": [
  { "type": "string", "format": "date" },
  { "type": "null" }
]

// oneOf: must match EXACTLY ONE schema (XOR)
"oneOf": [
  { "type": "string" },
  { "type": "integer" }
]

// not: must NOT match the schema
"not": { "type": "null" }  // disallow null

$ref and Schema Reuse

Complex schemas grow quickly. $ref lets you reference reusable sub-schemas to avoid repetition:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$defs": {
    "Address": {
      "type": "object",
      "properties": {
        "street": { "type": "string" },
        "city":   { "type": "string" },
        "pin":    { "type": "string", "pattern": "^[0-9]{6}$" }
      },
      "required": ["street", "city", "pin"]
    }
  },
  "type": "object",
  "properties": {
    "billingAddress":  { "$ref": "#/$defs/Address" },
    "shippingAddress": { "$ref": "#/$defs/Address" }
  }
}

Conditional Validation with if/then/else

JSON Schema 2019-09+ supports conditional logic without workarounds:

{
  "type": "object",
  "properties": {
    "paymentMethod": { "type": "string" },
    "cardNumber": { "type": "string" },
    "upiId": { "type": "string" }
  },
  "if":   { "properties": { "paymentMethod": { "const": "card" } } },
  "then": { "required": ["cardNumber"] },
  "else": { "required": ["upiId"] }
}

Format Keywords

The format keyword annotates the expected string format. By default it is advisory (validation libraries may or may not enforce it). Enable strict format validation explicitly in your validator configuration.

format valueDescription
date-timeISO 8601 datetime with timezone (e.g., "2026-06-07T10:30:00Z")
dateFull date: "2026-06-07"
timeFull time: "10:30:00Z"
emailRFC 5321 email address
uriRFC 3986 URI
uuidRFC 4122 UUID (e.g., "550e8400-e29b-41d4-a716-446655440000")
ipv4IPv4 address in dotted-decimal notation
ipv6IPv6 address
hostnameRFC 1123 hostname

Popular Validators by Language

  • JavaScript/TypeScript: ajv (most widely used, draft 2020-12 support)
  • Python: jsonschema, fastjsonschema
  • Java: networknt/json-schema-validator
  • Go: santhosh-tekuri/jsonschema
  • Rust: jsonschema crate
  • PHP: opis/json-schema
✅ Key Insight: additionalProperties

Setting "additionalProperties": false is the most impactful single addition to most schemas. It rejects any property not explicitly listed in properties, preventing typos and undocumented fields from silently passing through. Always set it for API request body schemas.