JSON Schema is a vocabulary that lets you describe the expected structure of a JSON document. You write a schema — itself a JSON object — and then any JSON document can be validated against it automatically. If the data matches the schema, it's valid. If it doesn't, you get detailed error messages telling you exactly what's wrong.
JSON Schema is used for API request and response validation, configuration file enforcement, form input validation, auto-generated documentation, and generating code (TypeScript interfaces, database models, form widgets). It is the foundation of OpenAPI and is supported by every major language.
The simplest valid schema
An empty object {} is a valid JSON Schema. It accepts any JSON value at all. In practice, you start adding keywords to restrict what values are valid.
{
"$schema": "https://json-schema.org/draft-07/schema",
"type": "object"
}
The $schema keyword declares which draft of JSON Schema you are using. It is optional but recommended — validators use it to choose the correct behaviour.
The type keyword
type restricts the kind of value allowed. Valid values are: "string", "number", "integer", "boolean", "array", "object", and "null". You can also pass an array to allow multiple types.
{ "type": "string" } // only strings pass
{ "type": "integer" } // only integers (no decimals)
{ "type": ["string", "null"] } // string or null (nullable field)
Describing objects: properties and required
properties defines the expected keys and their schemas. required is an array of key names that must be present. Any key not listed in properties is still allowed unless you add "additionalProperties": false.
{
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"email": { "type": "string", "format": "email" },
"age": { "type": "integer", "minimum": 0, "maximum": 120 }
},
"required": ["id", "name", "email"],
"additionalProperties": false
}
This schema validates that:
id,name, andemailmust be presentageis optional but must be an integer between 0 and 120 if provided- No extra keys (like
password) are allowed through
String constraints
When type is "string", additional keywords constrain the value further:
{
"type": "string",
"minLength": 3, // at least 3 characters
"maxLength": 50, // no more than 50 characters
"pattern": "^[a-z0-9]+$", // only lowercase letters and digits (regex)
"format": "email" // well-known format hint (validated by some tools)
}
Common format values: "email", "uri", "date" (YYYY-MM-DD), "date-time" (ISO 8601), "uuid", "ipv4". Format validation is optional — validators may only treat it as a hint unless you configure strict format checking.
Restricting to a fixed set: enum
enum limits a value to one of a fixed list. It works on any type.
{
"type": "string",
"enum": ["active", "inactive", "pending", "banned"]
}
Number constraints
{
"type": "number",
"minimum": 0, // value >= 0
"maximum": 100, // value <= 100
"exclusiveMinimum": 0, // value > 0 (draft-07 syntax)
"multipleOf": 0.01 // must be a multiple of 0.01 (useful for currency)
}
Array validation: items, minItems, maxItems
items defines the schema every element in the array must match. Use minItems and maxItems to constrain the length, and uniqueItems: true to prohibit duplicates.
{
"type": "array",
"items": { "type": "string" }, // every element must be a string
"minItems": 1, // array cannot be empty
"maxItems": 10, // no more than 10 elements
"uniqueItems": true // no duplicate values
}
To validate arrays where each position has a different type (tuples), use prefixItems in draft 2020-12, or an array value for items in draft-07:
// draft-07 tuple: [string, number, boolean]
{
"type": "array",
"items": [
{ "type": "string" },
{ "type": "number" },
{ "type": "boolean" }
],
"additionalItems": false
}
Nested objects
Schemas nest naturally — a property's schema can itself be a full schema with its own properties, required, and constraints.
{
"type": "object",
"properties": {
"name": { "type": "string" },
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"zip": { "type": "string", "pattern": "^\\d{5}$" }
},
"required": ["street", "city", "zip"]
}
},
"required": ["name", "address"]
}
Reusing definitions with $defs
To avoid repeating the same sub-schema in multiple places, define it once under $defs and reference it with $ref.
{
"$defs": {
"nonEmptyString": {
"type": "string",
"minLength": 1
}
},
"type": "object",
"properties": {
"firstName": { "$ref": "#/$defs/nonEmptyString" },
"lastName": { "$ref": "#/$defs/nonEmptyString" }
},
"required": ["firstName", "lastName"]
}
In draft-07, use definitions (without the dollar sign) instead of $defs.
A complete real-world example
This schema validates a user registration payload:
{
"$schema": "https://json-schema.org/draft-07/schema",
"title": "User Registration",
"description": "Schema for a new user sign-up payload",
"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
},
"role": {
"type": "string",
"enum": ["user", "admin", "moderator"],
"default": "user"
},
"tags": {
"type": "array",
"items": { "type": "string", "minLength": 1 },
"maxItems": 5,
"uniqueItems": true
}
},
"required": ["username", "email", "password"],
"additionalProperties": false
}
Valid input:
{
"username": "alice_dev",
"email": "alice@example.com",
"password": "s3cr3tpass",
"age": 25,
"role": "user",
"tags": ["javascript", "api"]
}
Invalid — would fail validation:
{
"username": "al", // too short — minLength: 3
"email": "not-an-email", // fails format: email
"password": "abc", // too short — minLength: 8
"role": "superadmin", // not in enum
"extra_field": true // additionalProperties: false
}
Validating in JavaScript with Ajv
Ajv is the fastest JSON Schema validator for JavaScript. Install it with npm install ajv.
import Ajv from 'ajv';
import addFormats from 'ajv-formats'; // for "email", "uri", "date-time" formats
const ajv = new Ajv();
addFormats(ajv);
const schema = {
type: 'object',
properties: {
username: { type: 'string', minLength: 3 },
email: { type: 'string', format: 'email' }
},
required: ['username', 'email']
};
const validate = ajv.compile(schema);
const data = { username: 'alice', email: 'alice@example.com' };
if (validate(data)) {
console.log('Valid ✓');
} else {
console.error(validate.errors);
// [{
// instancePath: '/email',
// message: 'must match format "email"',
// ...
// }]
}
Each error object includes instancePath (which field failed), message (human-readable reason), and keyword (which schema keyword triggered it). This makes it straightforward to map errors back to form fields.
JSON Schema versions
JSON Schema has gone through several drafts. The most important ones:
| Draft | $schema URI | Notes |
|---|---|---|
| Draft-07 | https://json-schema.org/draft-07/schema | Most widely supported; use this as default |
| Draft 2019-09 | https://json-schema.org/draft/2019-09/schema | Intermediate; limited adoption |
| Draft 2020-12 | https://json-schema.org/draft/2020-12/schema | Latest; $defs, prefixItems; used by OpenAPI 3.1 |
OpenAPI 3.0 uses a modified subset of draft-07. OpenAPI 3.1 adopted full draft 2020-12. If you are writing schemas for an OpenAPI spec, check which version your spec uses.
Validate JSON against a schema online
Paste your JSON into the JSON Validator to check it for syntax errors first. For schema validation, the full JSON Schema Validator tool coming soon allows you to validate any JSON document against your schema directly in the browser.