Home → JSON Schema Tutorial
JSON Schema Tutorial: Complete Guide from Beginner to Advanced (2026)
JSON Schema is the standard way to define and validate the structure of JSON data. If you're building APIs, processing config files, or validating form input, JSON Schema lets you describe exactly what your JSON should look like — and then validate any JSON document against that description automatically.
What Is JSON Schema?
JSON Schema is a vocabulary (a set of keywords) written in JSON that describes the structure of other JSON documents. Think of it like:
- A database schema that defines what columns and types a table has
- A TypeScript interface that describes what shape an object must have
- A form validation rule that says "this field is required and must be an email"
JSON Schema is specified as a JSON object with special keywords like type, required, properties, minimum, pattern, and many more. The current stable version is JSON Schema Draft 2020-12 (also called Draft 10).
Your First JSON Schema
The simplest possible schema — validates that a value is a string:
{ "type": "string" }
A slightly more useful schema — validates a user object:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/schemas/user.json",
"title": "User",
"description": "A registered user",
"type": "object",
"required": ["id", "name", "email"],
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"email": { "type": "string" }
}
}
The $schema and $id keywords are optional but recommended — they tell validators which version to use and give the schema a unique identifier.
JSON Schema Types
The type keyword is the most fundamental — it specifies what JSON type is allowed:
| Type | Matches | Example |
|---|---|---|
"string" | JSON strings | "hello" |
"number" | Any number (int or float) | 42, 3.14 |
"integer" | Whole numbers only | 42, -7 |
"boolean" | true or false | true |
"null" | null value | null |
"array" | JSON arrays | [1, 2, 3] |
"object" | JSON objects | {"key": "val"} |
You can allow multiple types using an array:
{ "type": ["string", "null"] } // accepts "hello" or null
String Validation Keywords
{
"type": "string",
"minLength": 3, // minimum character count
"maxLength": 100, // maximum character count
"pattern": "^[a-zA-Z]+$", // regex pattern (letters only)
"format": "email" // semantic format hint
}
Common format values: email uri date date-time time uuid ipv4 ipv6 hostname
Note: format validation is optional — validators may or may not enforce it. Use pattern for strict enforcement.
Number Validation Keywords
{
"type": "number",
"minimum": 0, // value must be >= 0
"maximum": 100, // value must be <= 100
"exclusiveMinimum": 0, // value must be > 0 (not equal)
"exclusiveMaximum": 100,// value must be < 100
"multipleOf": 5 // value must be divisible by 5: 0, 5, 10...
}
Object Validation: properties, required, additionalProperties
{
"type": "object",
"required": ["id", "name"], // these fields MUST be present
"properties": {
"id": { "type": "integer" },
"name": { "type": "string", "minLength": 1 },
"email": { "type": "string", "format": "email" },
"age": { "type": "integer", "minimum": 0 }
},
"additionalProperties": false, // reject any unknown fields
"minProperties": 2, // at least 2 properties
"maxProperties": 10 // at most 10 properties
}
additionalProperties is important for strict schemas. By default it's true (any extra fields are allowed). Set it to false to reject unknown fields, or set it to a schema to constrain extra fields' types.
Array Validation Keywords
{
"type": "array",
"items": { "type": "string" }, // each item must be a string
"minItems": 1, // at least 1 item
"maxItems": 10, // at most 10 items
"uniqueItems": true // no duplicate values
}
For arrays of objects (very common in APIs):
{
"type": "array",
"items": {
"type": "object",
"required": ["id", "name"],
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" }
}
}
}
Nested Objects (Real Example)
Here's a schema for a typical REST API user with a nested address:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "User",
"type": "object",
"required": ["id", "name", "email"],
"properties": {
"id": {
"type": "integer",
"description": "Unique user ID",
"minimum": 1
},
"name": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"email": {
"type": "string",
"format": "email"
},
"role": {
"type": "string",
"enum": ["admin", "editor", "viewer"]
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"country": { "type": "string", "minLength": 2, "maxLength": 2 },
"zip": { "type": "string", "pattern": "^[0-9]{5}$" }
},
"required": ["city", "country"]
},
"tags": {
"type": "array",
"items": { "type": "string" },
"uniqueItems": true
}
}
}
Reusing Schemas with $ref and $defs
When the same sub-schema appears in multiple places, define it once in $defs and reference it with $ref:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"Address": {
"type": "object",
"required": ["city", "country"],
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"country": { "type": "string" }
}
},
"PhoneNumber": {
"type": "string",
"pattern": "^\\+?[0-9\\s\\-]{7,15}$"
}
},
"type": "object",
"properties": {
"billingAddress": { "$ref": "#/$defs/Address" },
"shippingAddress": { "$ref": "#/$defs/Address" },
"phone": { "$ref": "#/$defs/PhoneNumber" }
}
}
Note: In older drafts (Draft 4–7), $defs was called definitions. If you see {"$ref": "#/definitions/..."}, that's the older syntax.
Composition Keywords: allOf, anyOf, oneOf, not
These keywords let you combine schemas:
allOf — must match ALL schemas
{
"allOf": [
{ "type": "object", "required": ["id"] },
{ "properties": { "name": { "type": "string" } } }
]
}
// Data must satisfy BOTH schemas
anyOf — must match AT LEAST ONE schema
{
"anyOf": [
{ "type": "string" },
{ "type": "number" }
]
}
// Data can be a string OR a number
oneOf — must match EXACTLY ONE schema
{
"oneOf": [
{ "type": "string", "maxLength": 5 },
{ "type": "string", "minLength": 10 }
]
}
// Must be a short string OR a long string, not something matching both
not — must NOT match the schema
{
"not": { "type": "null" }
}
// Any value except null
Conditional Validation: if / then / else
Apply different rules based on field values:
{
"type": "object",
"properties": {
"accountType": { "type": "string", "enum": ["personal", "business"] },
"companyName": { "type": "string" },
"ssn": { "type": "string" }
},
"if": { "properties": { "accountType": { "const": "business" } } },
"then": { "required": ["companyName"] },
"else": { "required": ["ssn"] }
}
This schema requires companyName if accountType is "business", and ssn otherwise.
The enum Keyword
Restrict a value to a fixed set of options:
{ "enum": ["red", "green", "blue"] }
// Accepts only these three strings
{ "type": "string", "enum": ["pending", "active", "suspended", "deleted"] }
// Status field with allowed values
The const Keyword
Requires the value to be exactly one specific value:
{ "const": "v2" }
// Only "v2" is valid — useful for versioned APIs
Complete API Schema Example
Here's a production-ready schema for a product catalog API:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://api.example.com/schemas/product.json",
"title": "Product",
"description": "A product in the catalog",
"type": "object",
"required": ["id", "name", "price", "currency", "inStock"],
"additionalProperties": false,
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique product identifier"
},
"sku": {
"type": "string",
"pattern": "^[A-Z]{2}-[0-9]{6}$",
"description": "Stock-keeping unit code, e.g. AB-123456"
},
"name": {
"type": "string",
"minLength": 1,
"maxLength": 200
},
"description": {
"type": ["string", "null"],
"maxLength": 2000
},
"price": {
"type": "number",
"minimum": 0,
"exclusiveMinimum": 0
},
"currency": {
"type": "string",
"enum": ["USD", "EUR", "GBP", "JPY"]
},
"inStock": {
"type": "boolean"
},
"stock": {
"type": "integer",
"minimum": 0
},
"categories": {
"type": "array",
"items": { "type": "string" },
"minItems": 1,
"uniqueItems": true
},
"images": {
"type": "array",
"items": {
"type": "object",
"required": ["url"],
"properties": {
"url": { "type": "string", "format": "uri" },
"alt": { "type": "string" },
"width": { "type": "integer", "minimum": 1 },
"height": { "type": "integer", "minimum": 1 }
}
}
},
"createdAt": { "type": "string", "format": "date-time" },
"updatedAt": { "type": "string", "format": "date-time" }
}
}
JSON Schema Versions
| Version | Release | Key Changes |
|---|---|---|
| Draft 4 | 2013 | First widely-adopted version |
| Draft 6 | 2017 | $ref improvements, exclusiveMinimum changes |
| Draft 7 | 2018 | if/then/else, readOnly/writeOnly, content encoding |
| Draft 2019-09 | 2019 | $defs (replaces definitions), $anchor, $recursiveRef |
| Draft 2020-12 | 2020 | prefixItems, $dynamicRef, improved $ref |
Recommendation: Use Draft 2020-12 for new projects. For maximum compatibility with older tools, Draft 7 is still widely supported. Always declare "$schema" to specify which version you're targeting.
Validating JSON Against a Schema (Code)
JavaScript: Ajv
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
const ajv = new Ajv();
addFormats(ajv); // adds format: "email", "date-time", etc.
const schema = {
type: 'object',
required: ['id', 'name'],
properties: {
id: { type: 'integer' },
name: { type: 'string', minLength: 1 },
email:{ type: 'string', format: 'email' }
}
};
const validate = ajv.compile(schema);
const data = { id: 1, name: 'Alice', email: 'alice@example.com' };
if (validate(data)) {
console.log('Valid!');
} else {
console.log('Errors:', validate.errors);
}
Python: jsonschema
from jsonschema import validate, ValidationError
schema = {
"type": "object",
"required": ["id", "name"],
"properties": {
"id": {"type": "integer"},
"name": {"type": "string", "minLength": 1}
}
}
data = {"id": 1, "name": "Alice"}
try:
validate(instance=data, schema=schema)
print("Valid!")
except ValidationError as e:
print(f"Invalid: {e.message}")
Generate a schema from your JSON
Paste any JSON object and get a ready-to-use JSON Schema automatically.
Generate JSON Schema →Frequently Asked Questions
string, number, integer, boolean, null, array, and object. Use the type keyword: {"type": "string"}. You can allow multiple types: {"type": ["string", "null"]}.required keyword at the object level as an array of field names: {"type": "object", "required": ["id", "name"], "properties": {...}}. Fields listed in required must be present in the data for validation to pass.allOf requires the data to satisfy ALL listed schemas. anyOf requires it to satisfy AT LEAST ONE. oneOf requires it to satisfy EXACTLY ONE (mutually exclusive schemas). Use anyOf for flexible types, allOf to extend/merge schemas, and oneOf when schemas are mutually exclusive.properties. Setting "additionalProperties": false makes the schema strict — it will reject any properties not listed in properties or patternProperties. This is useful for catching typos in field names.Draft 2020-12 (the latest stable version). For maximum compatibility with older tools (like OpenAPI 3.0), use Draft 7. Always declare "$schema": "https://json-schema.org/draft/2020-12/schema" in your schema file to make the version explicit.Related Tools & Guides
JSON Schema Generator | JSON Schema Validator | JSON Schema Examples | How to Validate JSON | JSONPath Cheatsheet | JSON to TypeScript