Everything you need to validate JSON data: types, required fields, nested objects, arrays, patterns, conditional logic, and production-ready best practices.
Test Your APIs →JSON Schema is a declarative language for describing the structure of JSON data. It lets you define what fields a JSON document should have, what types those fields should be, and what constraints apply — then validate any document against those rules automatically.
If you build or consume APIs, JSON Schema is the standard way to enforce data contracts. It is used by OpenAPI (Swagger), MongoDB, Kubernetes CRDs, GitHub Actions, VS Code settings, and hundreds of other tools.
A JSON Schema is itself a JSON object. Here is the simplest possible schema that validates any JSON value:
{}
An empty object means "accept everything." To make it useful, you add keywords that constrain the data.
Every JSON value falls into one of seven primitive types. Use the type keyword to restrict which type is allowed:
| Type | JSON Example | Notes |
|---|---|---|
string | "hello" | UTF-8 text |
number | 3.14 | Integer or floating-point |
integer | 42 | Whole numbers only |
boolean | true | true or false |
object | {"a": 1} | Key-value pairs |
array | [1, 2, 3] | Ordered list of values |
null | null | Explicit absence of value |
To describe an object with specific fields, combine type, properties, and type constraints:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "integer", "minimum": 0 },
"email": { "type": "string", "format": "email" }
}
}
This schema accepts an object where name is a string, age is a non-negative integer, and email follows the email format. All three fields are optional by default.
By default, every property in properties is optional. Use the required array to mandate specific fields:
{
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"role": { "type": "string", "default": "viewer" }
},
"required": ["id", "name"]
}
Here id and name must be present. role is optional and defaults to "viewer" in many implementations. Note that default is a documentation hint — most validators do not inject defaults unless explicitly configured.
"additionalProperties": false to reject any fields not listed in properties. This catches typos and prevents unexpected data from leaking into your system.
Real-world data is rarely flat. JSON Schema handles nesting naturally — define a schema for each level:
{
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"name": { "type": "string" },
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"zip": { "type": "string", "pattern": "^\\d{5}(-\\d{4})?$" }
},
"required": ["street", "city", "zip"]
}
},
"required": ["name", "address"]
}
},
"required": ["user"]
}
This validates a three-level deep structure: root → user → address, with a regex pattern on the zip code.
Use "type": "array" with the items keyword to validate array elements:
{
"type": "object",
"properties": {
"tags": {
"type": "array",
"items": { "type": "string" },
"minItems": 1,
"maxItems": 10,
"uniqueItems": true
},
"scores": {
"type": "array",
"items": { "type": "number", "minimum": 0, "maximum": 100 }
}
}
}
tags must be an array of 1–10 unique strings. scores must contain numbers between 0 and 100.
If your array has a fixed structure (like a coordinate pair), use prefixItems:
{
"type": "array",
"prefixItems": [
{ "type": "number", "minimum": -90, "maximum": 90 },
{ "type": "number", "minimum": -180, "maximum": 180 }
],
"items": false
}
This accepts [40.7128, -74.0060] but rejects extra elements because "items": false forbids them.
The pattern keyword applies a regular expression to string values. The regex follows ECMA-262 syntax:
{
"type": "object",
"properties": {
"phone": {
"type": "string",
"pattern": "^\\+?[1-9]\\d{1,14}$"
},
"slug": {
"type": "string",
"pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"
},
"hex_color": {
"type": "string",
"pattern": "^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
}
}
}
phone validates E.164 international numbers. slug enforces lowercase-hyphenated URL slugs. hex_color accepts 3- or 6-digit hex codes.
You can also use patternProperties to apply schemas to keys matching a regex:
{
"type": "object",
"patternProperties": {
"^env_": { "type": "string" },
"^count_": { "type": "integer", "minimum": 0 }
},
"additionalProperties": false
}
This accepts {"env_mode": "production", "count_users": 42} but rejects keys that do not match either pattern.
Sometimes validation rules depend on the value of another field. JSON Schema supports this with if, then, and else:
{
"type": "object",
"properties": {
"payment_method": { "type": "string", "enum": ["credit_card", "bank_transfer"] },
"card_number": { "type": "string" },
"routing_number": { "type": "string" }
},
"required": ["payment_method"],
"if": {
"properties": { "payment_method": { "const": "credit_card" } }
},
"then": {
"required": ["card_number"]
},
"else": {
"required": ["routing_number"]
}
}
When payment_method is "credit_card", the card_number field is required. For any other value, routing_number is required instead. This is much cleaner than using oneOf with duplicated schemas.
For more than two branches, wrap multiple if/then blocks inside allOf:
{
"type": "object",
"properties": {
"type": { "type": "string", "enum": ["personal", "business", "government"] }
},
"allOf": [
{
"if": { "properties": { "type": { "const": "business" } } },
"then": { "required": ["tax_id", "company_name"] }
},
{
"if": { "properties": { "type": { "const": "government" } } },
"then": { "required": ["agency_code", "jurisdiction"] }
}
]
}
The format keyword provides semantic validation for strings. It is advisory by default in Draft 2020-12 — enable format assertion in your validator for strict checking.
| Format | Validates | Example |
|---|---|---|
email | RFC 5321 email address | user@example.com |
uri | Full URI (RFC 3986) | https://helloandy.net/api-tester/ |
uri-reference | URI or relative reference | /api/v2/users |
date | ISO 8601 date | 2026-03-14 |
date-time | ISO 8601 date-time | 2026-03-14T10:30:00Z |
time | ISO 8601 time | 10:30:00+05:00 |
ipv4 | IPv4 address | 192.168.1.1 |
ipv6 | IPv6 address | ::1 |
uuid | RFC 4122 UUID | 550e8400-e29b-41d4-a716-446655440000 |
hostname | Internet hostname | api.example.com |
json-pointer | JSON Pointer (RFC 6901) | /user/address/city |
regex | Valid regex string | ^[a-z]+$ |
You do not need to build validation from scratch. Here are the best tools by language:
jsonschema library — the reference implementation, supports Draft 2020-12.gojsonschema — full Draft 7 support, simple API.networknt/json-schema-validator — high performance, supports Draft 2020-12.ajv-cli or check-jsonschema (Python) for CI/CD pipeline validation.import Ajv from "ajv";
import addFormats from "ajv-formats";
const ajv = new Ajv({ allErrors: true });
addFormats(ajv);
const schema = {
type: "object",
properties: {
email: { type: "string", format: "email" },
age: { type: "integer", minimum: 18 }
},
required: ["email", "age"]
};
const validate = ajv.compile(schema);
const data = { email: "test@example.com", age: 25 };
if (validate(data)) {
console.log("Valid!");
} else {
console.log(validate.errors);
}
from jsonschema import validate, ValidationError
schema = {
"type": "object",
"properties": {
"email": {"type": "string", "format": "email"},
"age": {"type": "integer", "minimum": 18}
},
"required": ["email", "age"]
}
data = {"email": "test@example.com", "age": 25}
try:
validate(instance=data, schema=schema)
print("Valid!")
except ValidationError as e:
print(f"Invalid: {e.message}")
Large schemas become unwieldy fast. JSON Schema provides composition keywords to split and reuse definitions:
{
"$defs": {
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"country": { "type": "string" }
},
"required": ["street", "city", "country"]
}
},
"type": "object",
"properties": {
"billing_address": { "$ref": "#/$defs/address" },
"shipping_address": { "$ref": "#/$defs/address" }
}
}
The $ref keyword lets you define address once and reuse it for both billing and shipping. For complex APIs, you can split schemas into separate files and reference them by URI.
allOf — data must match all listed schemas (intersection)oneOf — data must match exactly one schema (exclusive choice)anyOf — data must match at least one schema (union)not — data must not match the given schema (negation)Explicitly set "additionalProperties": false on objects that should not accept unknown fields. This prevents silent data loss and catches misspelled keys early. If you need extensibility, set it to true or provide a schema for the additional properties.
Extract repeated structures into $defs and reference them with $ref. This keeps schemas DRY and makes updates easier — change the definition in one place and every reference picks it up.
Client-side validation provides fast feedback. Server-side validation provides security. Use the same JSON Schema on both sides to guarantee consistency. Libraries like Ajv work in browsers and Node.js alike.
In Draft 2020-12, format is advisory by default. If you rely on it for validation (email, date, URI), configure your validator to enforce format assertions. In Ajv, pass { validateFormats: true } or use the ajv-formats plugin.
Add "title" and "description" to every property and definition. These appear in auto-generated docs, IDE tooltips, and validation error messages. Future-you will thank present-you.
Include a $id with a version identifier (e.g., "$id": "https://api.example.com/schemas/user/v2"). This lets consumers pin to a specific version and makes breaking changes explicit.
Write tests that cover valid data, each individual constraint violation, edge cases (empty strings, zero, null), and boundary values (minLength, maximum). Treat schemas as code — they deserve the same test coverage.
JSON (JavaScript Object Notation) is a data format for storing and exchanging structured data. JSON Schema is a separate specification that describes the expected structure, types, and constraints of a JSON document. Think of JSON as the data and JSON Schema as the blueprint that validates whether that data is correct.
Use Draft 2020-12 (the latest stable release) for new projects. It adds features like $dynamicRef and prefixItems for tuple validation. If you are maintaining an older codebase, Draft 7 and Draft 2019-09 are still widely supported by most validation libraries.
Yes. JSON Schema supports deep nesting through the properties keyword for objects and the items keyword for arrays. You can define schemas for each level of nesting, including arrays of objects, objects containing arrays, and any combination of the two.
Most validation libraries return structured error objects with a JSON Pointer path to the failing field. Use the title and description keywords in your schema to add human-readable context. Libraries like Ajv (JavaScript) and jsonschema (Python) support custom error messages and formatters.
OpenAPI (formerly Swagger) uses an extended subset of JSON Schema. OpenAPI 3.1 achieved full compatibility with JSON Schema Draft 2020-12. Earlier OpenAPI versions (3.0 and below) had differences such as lacking support for if/then/else and using nullable instead of type arrays. For API design, OpenAPI 3.1+ schemas are interchangeable with standard JSON Schema.
Send JSON payloads to any endpoint and see validation responses instantly — no setup required.
Open API Tester →