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.
| Keyword | Applies to | What it does |
|---|---|---|
type | Any | Restricts the JSON type: string, number, integer, boolean, null, array, object |
properties | Object | Defines schemas for specific keys in an object |
required | Object | Array of property names that must be present |
additionalProperties | Object | Controls whether keys not in properties are allowed |
items | Array | Schema every array element must match |
prefixItems | Array (2020-12) | Schemas for each positional element (tuple validation) |
minItems / maxItems | Array | Minimum and maximum number of array elements |
uniqueItems | Array | All array elements must be distinct |
minLength / maxLength | String | Minimum and maximum string length (in characters) |
pattern | String | String must match this regular expression |
format | String | Semantic format hint: date-time, email, uri, uuid, etc. |
enum | Any | Value must be one of the listed values |
const | Any | Value must equal this exact value |
minimum / maximum | Number | Inclusive lower and upper bounds |
exclusiveMinimum / exclusiveMaximum | Number | Exclusive lower and upper bounds |
multipleOf | Number | Value must be a multiple of this number |
allOf | Any | Must be valid against all listed schemas |
anyOf | Any | Must be valid against at least one listed schema |
oneOf | Any | Must be valid against exactly one listed schema |
not | Any | Must NOT be valid against the given schema |
if / then / else | Any | Conditional validation based on whether if matches |
$ref | Any | Reference to another schema definition (by URI or $defs pointer) |
$defs | Schema root | Named 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:
{"id": 1}— missing requiredemail{"id": "one", "email": "a@b.com"}—idis a string, not integer{"id": 1, "email": "a@b.com", "extra": true}— unknown key blocked byadditionalProperties: false{"id": 1, "email": "a@b.com", "role": "superuser"}—rolevalue not in enum
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 value | What it validates | Example |
|---|---|---|
date-time | ISO 8601 date-time string | "2026-05-22T10:30:00Z" |
date | ISO 8601 full date | "2026-05-22" |
time | ISO 8601 time | "10:30:00Z" |
email | Email address | "user@example.com" |
uri | Full URI | "https://example.com/path" |
uuid | UUID string | "550e8400-e29b-41d4-a716-..." |
ipv4 | IPv4 address | "192.168.1.1" |
ipv6 | IPv6 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 URI | Status |
|---|---|---|
| Draft 7 | https://json-schema.org/draft-07/schema | Most widely supported — safe default |
| Draft 2019-09 | https://json-schema.org/draft/2019-09/schema | Introduced $defs, $anchor |
| Draft 2020-12 | https://json-schema.org/draft/2020-12/schema | Current — prefixItems, fixed $ref + siblings |
Key changes in Draft 2020-12 vs Draft 7
$ref+ sibling keywords: In Draft 7, any sibling keywords next to$refwere ignored. In Draft 2020-12, sibling keywords are applied alongside the referenced schema. This is a breaking change.- Array tuple validation:
items(as an array) was replaced byprefixItems. The oldadditionalItemskeyword was replaced byitems(as a schema for non-prefix elements). containsimprovements: AddedminContainsandmaxContainskeywords to control how many array elements must match thecontainsschema.$dynamicRefand$dynamicAnchor: New mechanism for recursive schemas that need to be extended by subschemas.- Vocabulary system: Schemas can now declare which vocabularies they use, allowing custom keywords to be formally defined.
Common validation errors and how to fix them
| Error message | Cause | Fix |
|---|---|---|
| "must be string" | Value type does not match type keyword | Check 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 object | Add 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 false | Remove the unexpected key or add it to properties |
| "must match pattern" | String does not match the pattern regex | Check the regex against the value — use a regex tester to debug |
| "must be >= X" | Number is below the minimum value | Use a value that satisfies the minimum constraint |
| "must NOT be valid" | Value satisfies a not schema — which means it should be rejected | Invert the logic in your not subschema |
| "must match exactly one schema in oneOf" | Value matches zero or more than one oneOf subschemas | Check 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 7 | Move 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.