Getting started
Introduction
JSON Schema is a vocabulary for annotating and validating JSON documents. It's platform-independent and widely used for API validation.
Current Specification: Draft 2020-12
Meta-schema: https://json-schema.org/draft/2020-12/schema
Minimal Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/product.schema.json",
"title": "Product",
"description": "A product in the catalog",
"type": "object"
}
The $schema keyword declares which JSON Schema version is used.
Basic Types
{
"type": "string"
}
Seven basic types:
| Type | Description |
|---|---|
string |
Text values |
number |
Numeric values (float) |
integer |
Whole numbers |
boolean |
true or false |
array |
Ordered lists |
object |
Key-value pairs |
null |
Null value |
Multiple Types
{
"type": ["string", "number"]
}
Accepts either a string or a number.
Core Keywords
Schema Metadata
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/schemas/user.json",
"title": "User",
"description": "A registered user",
"default": {},
"examples": [
{
"name": "Alice",
"email": "alice@example.com"
}
]
}
| Keyword | Purpose |
|---|---|
$schema |
Declares schema version |
$id |
Unique identifier (URI) |
title |
Human-readable title |
description |
Detailed explanation |
default |
Default value |
examples |
Example valid data |
References
{
"$defs": {
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" }
}
}
},
"type": "object",
"properties": {
"billing": { "$ref": "#/$defs/address" },
"shipping": { "$ref": "#/$defs/address" }
}
}
Use $ref to reuse schema definitions.
External References
{
"type": "object",
"properties": {
"user": {
"$ref": "https://example.com/schemas/user.json"
}
}
}
Reference schemas from external files or URLs.
String Validation
Length Constraints
{
"type": "string",
"minLength": 3,
"maxLength": 20
}
Restricts string length (inclusive).
Pattern Matching
{
"type": "string",
"pattern": "^[A-Za-z0-9_]+$"
}
Validates against a regular expression (ECMA-262).
Format Validation
{
"type": "string",
"format": "email"
}
Built-in format validators:
Date/Time Formats
| Format | Example |
|---|---|
date-time |
2024-12-31T23:59:59Z |
date |
2024-12-31 |
time |
23:59:59 |
duration |
P3Y6M4DT12H30M5S |
Network Formats
| Format | Example |
|---|---|
email |
user@example.com |
idn-email |
用户@例え.jp |
hostname |
example.com |
idn-hostname |
例え.jp |
ipv4 |
192.168.1.1 |
ipv6 |
::1 |
URI Formats
| Format | Description |
|---|---|
uri |
Full URI |
uri-reference |
URI or relative ref |
iri |
Internationalized URI |
iri-reference |
IRI or relative ref |
uri-template |
RFC 6570 template |
Other Formats
| Format | Description |
|---|---|
uuid |
RFC 4122 UUID |
json-pointer |
RFC 6901 pointer |
relative-json-pointer |
Relative pointer |
regex |
Regular expression |
Number Validation
Range Constraints
{
"type": "number",
"minimum": 0,
"maximum": 100
}
Inclusive minimum and maximum values.
Exclusive Range
{
"type": "number",
"exclusiveMinimum": 0,
"exclusiveMaximum": 100
}
Exclusive boundaries (0 < x < 100).
Multiple Of
{
"type": "number",
"multipleOf": 0.01
}
Value must be a multiple of the given number (useful for currency: 2 decimal places).
Integer Type
{
"type": "integer",
"minimum": 1,
"maximum": 10
}
Integers are a subset of numbers (no decimals).
Array Validation
Items Schema
{
"type": "array",
"items": {
"type": "string"
}
}
All items must match the schema (homogeneous array).
Tuple Validation
{
"type": "array",
"prefixItems": [
{ "type": "number" },
{ "type": "string" },
{ "type": "boolean" }
]
}
Validates positional items (heterogeneous array/tuple).
Array Length
{
"type": "array",
"minItems": 1,
"maxItems": 10
}
Constrains array size.
Unique Items
{
"type": "array",
"uniqueItems": true
}
All items must be unique (set semantics).
Contains
{
"type": "array",
"contains": {
"type": "string",
"pattern": "^admin"
},
"minContains": 1,
"maxContains": 3
}
Array must contain at least minContains items matching the schema.
Object Validation
Properties
{
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "integer" },
"email": {
"type": "string",
"format": "email"
}
}
}
Defines expected properties and their schemas.
Required Properties
{
"type": "object",
"properties": {
"name": { "type": "string" },
"email": { "type": "string" }
},
"required": ["name", "email"]
}
Lists mandatory properties.
Additional Properties
{
"type": "object",
"properties": {
"name": { "type": "string" }
},
"additionalProperties": false
}
Set to false to disallow unlisted properties.
{
"type": "object",
"properties": {
"name": { "type": "string" }
},
"additionalProperties": {
"type": "string"
}
}
Or provide a schema for additional properties.
Pattern Properties
{
"type": "object",
"patternProperties": {
"^[0-9]+$": {
"type": "number"
},
"^[a-z]+$": {
"type": "string"
}
}
}
Schema for properties matching regex patterns.
Property Names
{
"type": "object",
"propertyNames": {
"pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
}
}
Validates all property names against a schema.
Size Constraints
{
"type": "object",
"minProperties": 1,
"maxProperties": 10
}
Limits number of properties in the object.
Combining Schemas
allOf (AND)
{
"allOf": [
{ "type": "object" },
{
"properties": {
"name": { "type": "string" }
}
},
{
"properties": {
"age": { "type": "integer" }
}
}
]
}
Data must be valid against all subschemas.
anyOf (OR)
{
"anyOf": [{ "type": "string" }, { "type": "number" }]
}
Data must be valid against at least one subschema.
oneOf (XOR)
{
"oneOf": [
{
"properties": {
"type": { "const": "credit_card" },
"card_number": { "type": "string" }
}
},
{
"properties": {
"type": { "const": "bank_account" },
"account_number": { "type": "string" }
}
}
]
}
Data must be valid against exactly one subschema.
not (NOT)
{
"not": {
"type": "null"
}
}
Data must not be valid against the subschema.
Conditional Validation
if/then/else
{
"type": "object",
"properties": {
"country": { "type": "string" },
"postal_code": { "type": "string" }
},
"if": {
"properties": {
"country": { "const": "USA" }
}
},
"then": {
"properties": {
"postal_code": {
"pattern": "^[0-9]{5}(-[0-9]{4})?$"
}
}
},
"else": {
"properties": {
"postal_code": {
"pattern": "^[A-Z0-9]+$"
}
}
}
}
Apply different schemas based on conditions.
Dependent Schemas
{
"type": "object",
"properties": {
"credit_card": { "type": "string" }
},
"dependentSchemas": {
"credit_card": {
"properties": {
"billing_address": { "type": "string" }
},
"required": ["billing_address"]
}
}
}
When credit_card is present, billing_address becomes required.
Dependent Required
{
"type": "object",
"properties": {
"name": { "type": "string" },
"credit_card": { "type": "string" },
"billing_address": { "type": "string" }
},
"dependentRequired": {
"credit_card": ["billing_address"]
}
}
Simpler syntax when only requiring fields conditionally.
Generic Keywords
Enumeration
{
"type": "string",
"enum": ["red", "green", "blue"]
}
Value must be one of the enumerated values.
Constant
{
"type": "string",
"const": "active"
}
Value must exactly match the constant.
Annotations
{
"type": "string",
"deprecated": true,
"readOnly": true,
"writeOnly": false
}
| Keyword | Purpose |
|---|---|
deprecated |
Mark as deprecated |
readOnly |
For API responses only |
writeOnly |
For API requests only |
Complete Examples
User Registration Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/user-registration.schema.json",
"title": "User Registration",
"description": "Schema for user registration form",
"type": "object",
"properties": {
"username": {
"type": "string",
"minLength": 3,
"maxLength": 20,
"pattern": "^[a-zA-Z0-9_]+$",
"description": "Unique username (alphanumeric and underscore)"
},
"email": {
"type": "string",
"format": "email",
"description": "Valid email address"
},
"password": {
"type": "string",
"minLength": 8,
"pattern": "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]+$",
"description": "Password with uppercase, lowercase, digit, and special char"
},
"age": {
"type": "integer",
"minimum": 18,
"maximum": 120,
"description": "User must be 18 or older"
},
"terms_accepted": {
"type": "boolean",
"const": true,
"description": "User must accept terms"
}
},
"required": ["username", "email", "password", "terms_accepted"]
}
Product Catalog Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/product.schema.json",
"title": "Product",
"description": "A product in the catalog",
"type": "object",
"$defs": {
"price": {
"type": "number",
"minimum": 0,
"multipleOf": 0.01
},
"dimension": {
"type": "object",
"properties": {
"length": { "type": "number", "minimum": 0 },
"width": { "type": "number", "minimum": 0 },
"height": { "type": "number", "minimum": 0 }
},
"required": ["length", "width", "height"]
}
},
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Unique product identifier"
},
"name": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"price": {
"$ref": "#/$defs/price"
},
"discount_price": {
"$ref": "#/$defs/price"
},
"tags": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"uniqueItems": true,
"minItems": 1
},
"dimensions": {
"$ref": "#/$defs/dimension"
},
"in_stock": {
"type": "boolean"
},
"category": {
"type": "string",
"enum": ["electronics", "clothing", "food", "books", "other"]
}
},
"required": ["id", "name", "price", "in_stock", "category"],
"if": {
"properties": {
"discount_price": { "type": "number" }
},
"required": ["discount_price"]
},
"then": {
"properties": {
"discount_price": {
"type": "number",
"exclusiveMaximum": { "$data": "1/price" }
}
}
}
}
API Response with Polymorphism
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/api-response.schema.json",
"title": "API Response",
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": ["success", "error"]
}
},
"required": ["status"],
"oneOf": [
{
"properties": {
"status": { "const": "success" },
"data": {
"type": "object",
"description": "Response payload"
}
},
"required": ["data"]
},
{
"properties": {
"status": { "const": "error" },
"error": {
"type": "object",
"properties": {
"code": { "type": "integer" },
"message": { "type": "string" }
},
"required": ["code", "message"]
}
},
"required": ["error"]
}
]
}
Validators & Tools
JavaScript
npm install ajv
import Ajv from "ajv";
import addFormats from "ajv-formats";
const ajv = new Ajv();
addFormats(ajv);
const schema = {
type: "object",
properties: {
email: { type: "string", format: "email" },
},
required: ["email"],
};
const validate = ajv.compile(schema);
const valid = validate({ email: "user@example.com" });
if (!valid) {
console.log(validate.errors);
}
Popular libraries:
ajv- Fast and feature-complete@hyperjump/json-schema- Spec-compliantjsonschema- Simple validator
Python
pip install jsonschema
from jsonschema import validate, ValidationError
schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer", "minimum": 0}
},
"required": ["name"]
}
data = {"name": "Alice", "age": 30}
try:
validate(instance=data, schema=schema)
print("Valid!")
except ValidationError as e:
print(f"Invalid: {e.message}")
Popular libraries:
jsonschema- Reference implementationfastjsonschema- High performance
Other Languages
| Language | Library |
|---|---|
| Java | json-schema-validator (networknt) |
| Go | gojsonschema |
| .NET/C# | Newtonsoft.Json.Schema |
| Ruby | json-schema gem |
| PHP | justinrainbow/json-schema |
| Rust | jsonschema crate |
Gotchas
Schema vs. Meta-schema
Schema validates your data.
Meta-schema validates your schema.
Always include $schema to declare which JSON Schema version you're using.
additionalProperties Default
By default, additionalProperties is true (allows any extra properties).
{
"type": "object",
"properties": {
"name": { "type": "string" }
}
}
This allows { "name": "Alice", "age": 30 }.
To disallow: set "additionalProperties": false.
Pattern is Anchored
Patterns are implicitly anchored (match entire string).
{ "pattern": "abc" }
Matches "abc" anywhere in the string. Use ^abc$ for exact match.
Format is Optional
The format keyword is for validation but may be ignored by validators.
Always check your validator's format support. Use ajv-formats with AJV.
$ref Replaces Sibling Keywords
In older drafts, sibling keywords to $ref were ignored.
{
"$ref": "#/$defs/address",
"description": "This might be ignored!"
}
Draft 2019-09+: Sibling keywords are now respected.
Integer vs. Number
integer is a distinct type from number.
{ "type": "integer" }
Rejects 1.0 even though mathematically equal to 1 in some validators.
Empty Arrays and Objects
Empty arrays/objects are valid by default.
Use minItems: 1 or minProperties: 1 to require content.
Also see
- JSON Schema Official Site - Official documentation
- JSON Schema Store - Collection of schemas
- Understanding JSON Schema - Comprehensive guide
- JSON Schema Validator - Online validation tool
- JSON Schema Cheatsheet - Quick reference by forivall
- AJV Documentation - Most popular JavaScript validator
- Draft 2020-12 Specification - Official spec