JSON Schema Complete Guide – Keywords, Types, and Examples

JSON Schema is a vocabulary for describing and validating the structure of JSON documents. You write a schema — itself a JSON object — that declares the expected types, required fields, value constraints, and structure. Any JSON document can then be validated against that schema automatically, returning detailed error messages for every violation.

Validate a schema in your browser — free, no signup:

Open JSON Schema Validator →

Core keywords reference

JSON Schema is built from a set of keywords. Each keyword adds one constraint. A document must satisfy all keywords in the schema to be valid.

KeywordApplies toWhat it does
typeAnyRestricts the JSON type: string, number, integer, boolean, null, array, object
propertiesObjectDefines schemas for specific keys in an object
requiredObjectArray of property names that must be present
additionalPropertiesObjectControls whether keys not in properties are allowed
itemsArraySchema every array element must match
prefixItemsArray (2020-12)Schemas for each positional element (tuple validation)
minItems / maxItemsArrayMinimum and maximum number of array elements
uniqueItemsArrayAll array elements must be distinct
minLength / maxLengthStringMinimum and maximum string length (in characters)
patternStringString must match this regular expression
formatStringSemantic format hint: date-time, email, uri, uuid, etc.
enumAnyValue must be one of the listed values
constAnyValue must equal this exact value
minimum / maximumNumberInclusive lower and upper bounds
exclusiveMinimum / exclusiveMaximumNumberExclusive lower and upper bounds
multipleOfNumberValue must be a multiple of this number
allOfAnyMust be valid against all listed schemas
anyOfAnyMust be valid against at least one listed schema
oneOfAnyMust be valid against exactly one listed schema
notAnyMust NOT be valid against the given schema
if / then / elseAnyConditional validation based on whether if matches
$refAnyReference to another schema definition (by URI or $defs pointer)
$defsSchema rootNamed reusable schema definitions

The type keyword

Every schema starts with type. JSON has six primitive types:

{ "type": "string" }     // accepts: "hello", ""
{ "type": "number" }     // accepts: 1, 3.14, -5
{ "type": "integer" }    // accepts: 1, -5  rejects: 3.14
{ "type": "boolean" }    // accepts: true, false
{ "type": "null" }       // accepts: null
{ "type": "array" }      // accepts: [], [1, 2, 3]
{ "type": "object" }     // accepts: {}, {"key": "val"}

You can allow multiple types by passing an array:

{ "type": ["string", "null"] }   // accepts: "hello" or null

Objects: properties, required, additionalProperties

Object schemas define what keys are expected and whether unknown keys are allowed:

{
  "type": "object",
  "properties": {
    "id":    { "type": "integer" },
    "email": { "type": "string", "format": "email" },
    "role":  { "type": "string", "enum": ["admin", "user", "viewer"] }
  },
  "required": ["id", "email"],
  "additionalProperties": false
}

This schema accepts {"id": 1, "email": "a@b.com", "role": "admin"} but rejects:

Important: required only checks that the key exists. It does not validate the value. A required field can be null, "", or 0 and will still pass. See JSON Schema required keyword for the full explanation and workarounds.

Strings: minLength, maxLength, pattern, format

{
  "type": "string",
  "minLength": 2,
  "maxLength": 50,
  "pattern": "^[A-Za-z ]+$"
}

The format keyword adds semantic intent beyond syntax. Common values:

Format valueWhat it validatesExample
date-timeISO 8601 date-time string"2026-05-22T10:30:00Z"
dateISO 8601 full date"2026-05-22"
timeISO 8601 time"10:30:00Z"
emailEmail address"user@example.com"
uriFull URI"https://example.com/path"
uuidUUID string"550e8400-e29b-41d4-a716-..."
ipv4IPv4 address"192.168.1.1"
ipv6IPv6 address"::1"

Note: validators are not required to enforce format by default. In Ajv, pass { "format": "fast" } or use the ajv-formats plugin to enable format validation.

Numbers: minimum, maximum, multipleOf

{
  "type": "integer",
  "minimum": 1,
  "maximum": 100,
  "multipleOf": 5
}
// Accepts: 5, 10, 15, ..., 100
// Rejects: 0 (below minimum), 101 (above maximum), 7 (not a multiple of 5)

Use exclusiveMinimum and exclusiveMaximum when the boundary value itself should be rejected:

{ "type": "number", "exclusiveMinimum": 0 }   // accepts: 0.001 — rejects: 0

Arrays: items, minItems, uniqueItems

{
  "type": "array",
  "items": { "type": "string" },
  "minItems": 1,
  "maxItems": 10,
  "uniqueItems": true
}
// Accepts: ["read", "write"]
// Rejects: []  (empty — violates minItems)
// Rejects: ["read", "read"]  (duplicate — violates uniqueItems)
// Rejects: ["read", 42]  (number — violates items type)

Tuple validation with prefixItems (Draft 2020-12)

When each array position has a specific meaning (a tuple), use prefixItems:

{
  "type": "array",
  "prefixItems": [
    { "type": "string" },
    { "type": "number" },
    { "type": "boolean" }
  ],
  "items": false
}
// Accepts: ["Alice", 28, true]
// Rejects: ["Alice", "28", true]  (position 1 must be number)
// Rejects: ["Alice", 28, true, "extra"]  (items:false blocks extra elements)

In Draft 7, tuple validation used an array for items and additionalItems: false instead.

Composition: allOf, anyOf, oneOf, not

These keywords combine schemas logically:

allOf — must match every schema

{
  "allOf": [
    { "type": "string" },
    { "minLength": 3 },
    { "pattern": "^[a-z]" }
  ]
}
// All three must be satisfied: string, at least 3 chars, starts with lowercase

anyOf — must match at least one schema

{
  "anyOf": [
    { "type": "string" },
    { "type": "number" }
  ]
}
// Accepts: "hello", 42 — rejects: true, null, {}

oneOf — must match exactly one schema

{
  "oneOf": [
    { "type": "integer" },
    { "type": "number", "minimum": 0.5 }
  ]
}
// 3 matches integer only → valid
// 0.7 matches the second schema only → valid
// 1.5 matches BOTH → invalid (oneOf requires exactly one match)

not — must NOT match the schema

{ "not": { "type": "null" } }
// Accepts anything that is not null

Conditional validation: if / then / else

Apply different validation rules based on a condition:

{
  "type": "object",
  "properties": {
    "country": { "type": "string" },
    "postalCode": { "type": "string" }
  },
  "if": {
    "properties": { "country": { "const": "US" } },
    "required": ["country"]
  },
  "then": {
    "properties": {
      "postalCode": { "pattern": "^[0-9]{5}(-[0-9]{4})?$" }
    }
  },
  "else": {
    "properties": {
      "postalCode": { "minLength": 3 }
    }
  }
}

When country is "US", postalCode must match a US ZIP pattern. For any other country, it only needs to be at least 3 characters.

Reusable definitions with $defs and $ref

Define schemas once, reference them multiple times:

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

In Draft 7, the definitions section was named definitions instead of $defs. Both work in most validators, but $defs is the standard from Draft 2019-09 onwards.

Complete real-world example

A schema for a user registration API request:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "User Registration",
  "type": "object",
  "properties": {
    "username": {
      "type": "string",
      "minLength": 3,
      "maxLength": 30,
      "pattern": "^[a-zA-Z0-9_]+$"
    },
    "email": {
      "type": "string",
      "format": "email"
    },
    "password": {
      "type": "string",
      "minLength": 8
    },
    "age": {
      "type": "integer",
      "minimum": 13
    },
    "roles": {
      "type": "array",
      "items": { "type": "string", "enum": ["user", "admin", "moderator"] },
      "minItems": 1,
      "uniqueItems": true
    },
    "newsletter": {
      "type": "boolean"
    }
  },
  "required": ["username", "email", "password"],
  "additionalProperties": false
}

This schema accepts:

{
  "username": "alice_dev",
  "email": "alice@example.com",
  "password": "s3cr3tP@ss",
  "age": 25,
  "roles": ["user"],
  "newsletter": true
}

JSON Schema draft versions

JSON Schema has evolved through several draft versions. The draft is declared in the $schema property at the top of your schema:

Draft$schema URIStatus
Draft 7https://json-schema.org/draft-07/schemaMost widely supported — safe default
Draft 2019-09https://json-schema.org/draft/2019-09/schemaIntroduced $defs, $anchor
Draft 2020-12https://json-schema.org/draft/2020-12/schemaCurrent — prefixItems, fixed $ref + siblings

Key changes in Draft 2020-12 vs Draft 7

Common validation errors and how to fix them

Error messageCauseFix
"must be string"Value type does not match type keywordCheck that the value is the correct JSON type — 42 is not a string, "42" is
"must have required property 'x'"A key listed in required is absent from the objectAdd the missing key, even if the value is empty or null
"must NOT have additional properties"A key is present that is not listed in properties and additionalProperties is falseRemove the unexpected key or add it to properties
"must match pattern"String does not match the pattern regexCheck the regex against the value — use a regex tester to debug
"must be >= X"Number is below the minimum valueUse a value that satisfies the minimum constraint
"must NOT be valid"Value satisfies a not schema — which means it should be rejectedInvert the logic in your not subschema
"must match exactly one schema in oneOf"Value matches zero or more than one oneOf subschemasCheck that your subschemas are mutually exclusive (no value can satisfy two at once)
"$ref is ignored" (Draft 7 validators)Keywords next to $ref are silently discarded in Draft 7Move sibling keywords inside the $defs target, or upgrade to Draft 2020-12

Validate your schema now

Paste your JSON and schema into the validator to see errors line by line — supports Draft 7 and Draft 2020-12.

Open JSON Schema Validator →

Frequently Asked Questions

What is JSON Schema used for?

JSON Schema is used to validate API request and response payloads, enforce configuration file structure, generate TypeScript interfaces and database models, power form validation, and auto-generate documentation. It is the foundation of OpenAPI (Swagger) and is supported in every major programming language via libraries like Ajv (JavaScript), jsonschema (Python), and Newtonsoft.Json.Schema (C#).

What is the difference between Draft 7 and Draft 2020-12?

The most important differences: In Draft 7, any keyword next to a $ref is silently ignored — in Draft 2020-12, they are applied. Tuple validation changed from an array-style items to prefixItems. Draft 2020-12 also added $dynamicRef for extensible recursive schemas. For most schemas that don't use these features, both drafts behave identically. Always declare "$schema" at the top so validators know which version to use.

How do I validate JSON against a schema in JavaScript?

Use the Ajv library: const Ajv = require('ajv/dist/2020'); const ajv = new Ajv(); const validate = ajv.compile(schema); const valid = validate(data);. If valid is false, validate.errors contains the full list of validation errors with paths and messages.

Does JSON Schema validate that required fields have non-empty values?

No. required only checks that the property key is present — not that its value is meaningful. A required field can be "", null, 0, or false and still pass. To enforce a non-empty string, add "minLength": 1 to the property schema. To reject null, use "type": "string" without "null" in the type array.

Can JSON Schema validate the values inside an array?

Yes. Use items to require every element to match a schema: {"type": "array", "items": {"type": "string"}}. For tuple-style arrays where each position has a specific type, use prefixItems (Draft 2020-12) or an array-style items (Draft 7). Add "items": false (or "additionalItems": false in Draft 7) to block elements beyond the defined positions.

About the author

Pasindu Ishan is a software developer based in Sri Lanka. He builds developer tools at JSON Dev Tools.