JSON Schema is the most widely used way to describe and validate JSON data. It powers OpenAPI specifications, AsyncAPI, npm's package validation, and countless production APIs. This guide covers everything you need to know — from the basics to advanced composition.
What is JSON Schema?
JSON Schema is a vocabulary for describing the structure of JSON data. A schema is itself a JSON document that specifies what fields a JSON document should have, what types those fields should be, what values are allowed, and how fields relate to each other.
Think of it as a type system for JSON: a way to express constraints that any JSON Schema validator can check. The same schema works in any language with a JSON Schema implementation, which means you get cross-language validation from a single source of truth.
Your first schema
Let's start simple. Here's a schema for a user object:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["id", "name", "email"],
"properties": {
"id": { "type": "integer", "minimum": 1 },
"name": { "type": "string", "minLength": 1 },
"email": { "type": "string", "format": "email" },
"age": { "type": "integer", "minimum": 0, "maximum": 150 }
}
}
This schema says: the document must be an object; it must have id, name, and email; each field has type and value constraints; age is optional but if present must be a non-negative integer at most 150.
A document that passes:
{ "id": 42, "name": "Alice", "email": "alice@example.com", "age": 30 }
A document that fails (missing email, age too high):
{ "id": 42, "name": "Alice", "age": 200 }
// Errors:
// - Missing required property: email
// - /age must be <= 150
Core keywords
type — restricts the JSON type. Values: string, number, integer, boolean, null, array, object. Can be a single string or an array for unions.
required — array of property names that must be present on an object. Without this, all properties are optional.
properties — defines schemas for individual properties of an object.
additionalProperties — controls properties not listed in properties. Set to false to forbid them; set to a schema to constrain them.
String constraints
{
"type": "string",
"minLength": 8,
"maxLength": 64,
"pattern": "^[A-Za-z0-9_-]+$",
"format": "uri"
}
Common format values: email, uri, uuid, date, time, date-time, ipv4, ipv6, hostname.
Number constraints
{
"type": "number",
"minimum": 0,
"maximum": 1,
"exclusiveMaximum": true,
"multipleOf": 0.01
}
This allows numbers from 0 inclusive up to (but not including) 1, with at most two decimal places.
Array constraints
{
"type": "array",
"items": { "type": "string" },
"minItems": 1,
"maxItems": 100,
"uniqueItems": true
}
items can be a single schema (all items must match it) or an array of schemas (tuple validation — first item matches the first schema, etc.).
Composition: allOf, anyOf, oneOf
These keywords combine schemas:
allOf — must match every subschema. Useful for inheritance:
{
"allOf": [
{ "$ref": "#/definitions/Animal" },
{ "properties": { "breed": { "type": "string" } } }
]
}
anyOf — must match at least one subschema. Useful for unions:
{
"anyOf": [
{ "type": "string" },
{ "type": "number" }
]
}
oneOf — must match exactly one. Useful for discriminated unions:
{
"oneOf": [
{ "properties": { "type": { "const": "circle" }, "radius": { "type": "number" } }, "required": ["type", "radius"] },
{ "properties": { "type": { "const": "square" }, "side": { "type": "number" } }, "required": ["type", "side"] }
]
}
References and reuse
Use $ref to reference another part of the schema:
{
"definitions": {
"Address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" }
}
}
},
"type": "object",
"properties": {
"billing": { "$ref": "#/definitions/Address" },
"shipping": { "$ref": "#/definitions/Address" }
}
}
References can point to local definitions (as above) or external URLs. External refs require the validator to fetch them, which has security and performance implications.
Drafts: which version to use?
- Draft 7 (2018) — the most widely used. OpenAPI 3.0 is based on it. If in doubt, use Draft 7.
- Draft 2019-09 — adds
unevaluatedProperties, content validation refinements. - Draft 2020-12 — the current draft. OpenAPI 3.1 uses it. Changes how tuples and arrays are handled.
The differences are mostly additive — Draft 7 schemas continue to work with newer validators. Pick a version based on what your tools support.
Common patterns
Closed schema (no extra properties allowed):
{ "type": "object", "additionalProperties": false, "properties": { ... } }
Enum (limited values):
{ "type": "string", "enum": ["pending", "active", "suspended", "deleted"] }
Nullable field:
{ "type": ["string", "null"] }
Required only sometimes (dependentRequired):
{
"type": "object",
"properties": {
"credit_card": { "type": "string" },
"billing_address": { "type": "string" }
},
"dependentRequired": {
"credit_card": ["billing_address"]
}
}
If credit_card is present, billing_address must also be present.
Validating in code
In JavaScript with AJV:
const Ajv = require("ajv");
const ajv = new Ajv();
const validate = ajv.compile(schema);
if (!validate(data)) {
console.log(validate.errors);
}
In Python with jsonschema:
from jsonschema import validate, ValidationError
try:
validate(instance=data, schema=schema)
except ValidationError as e:
print(e.message)
For quick interactive validation, our JSON Schema Validator works in the browser.
Where JSON Schema doesn't fit
JSON Schema is excellent for structural validation but limited for cross-field business rules. If you need to validate "either field A or field B must be present, but not both, and if A is present then field C's value must depend on A," you'll find JSON Schema awkward. For complex business rules, validate structure with JSON Schema and add business-rule checks separately in code.
Generating schemas from data
Tools like quicktype can infer a schema from sample JSON. This is a fast way to bootstrap a schema, but the inference is conservative — it makes all fields optional and uses broad types. Always tighten the generated schema by hand.
Wrap-up
JSON Schema is one of the highest-leverage tools in modern API development. A good schema serves as documentation, validation, code generation source, and contract test — all from a single artifact.
Start simple: define types and required fields. Add constraints as you learn what shapes your data takes. Use composition when your data has variation. And version your schemas alongside your APIs — schemas are part of your interface.