JSON Schema is a vocabulary for describing the structure, constraints, and types of a JSON document. It lets you define exactly what valid JSON should look like — which keys are required, what type each value must be, which values are allowed — and then validate any JSON document against those rules automatically. This guide explains what JSON Schema is, how to write one, and how to use it to validate JSON in JavaScript, Python, and your browser.
What Is JSON Schema?
JSON Schema is itself a JSON document. It describes another JSON document the same way a database schema describes a table: it declares the expected shape, types, and constraints of the data. A JSON Schema tells validators — libraries, APIs, form generators, IDEs — what "valid" means for a particular piece of JSON.
The specification is maintained at json-schema.org and is currently on Draft 2020-12. It's used by OpenAPI (the standard for documenting REST APIs), JSON Forms (UI generation from schemas), IDE tooling (VS Code uses JSON Schema to power autocomplete in package.json and tsconfig.json), and countless internal data pipelines.
A Minimal JSON Schema Example
Here's a JSON document representing a user, followed by a schema that describes its expected structure:
// The JSON document being validated
{
"id": 42,
"name": "Alice",
"email": "alice@example.com",
"age": 30,
"active": true
}// The JSON Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["id", "name", "email"],
"properties": {
"id": { "type": "integer" },
"name": { "type": "string", "minLength": 1 },
"email": { "type": "string", "format": "email" },
"age": { "type": "integer", "minimum": 0, "maximum": 150 },
"active": { "type": "boolean" }
},
"additionalProperties": false
} This schema says: the document must be an object; id, name, and email are required; age must be between 0 and 150 if present; and no keys other than the five listed are allowed.
The Core Keywords
type
Specifies the JSON type of a value. Valid types are "string", "number", "integer", "boolean", "array", "object", and "null". You can allow multiple types with an array:
{ "type": ["string", "null"] } // the value can be a string or nullproperties
Defines the schema for each key in an object. Each entry is itself a schema:
{
"type": "object",
"properties": {
"firstName": { "type": "string" },
"lastName": { "type": "string" },
"age": { "type": "integer", "minimum": 0 }
}
}required
An array of key names that must be present in the object. A key listed under properties but not in required is optional — it may be absent, but if present it must match its schema.
items
Defines the schema for every element in an array. This schema requires an array of strings:
{
"type": "array",
"items": { "type": "string" }
}String constraints
{
"type": "string",
"minLength": 1,
"maxLength": 100,
"pattern": "^[a-zA-Z0-9_]+quot; // regex pattern
}Number constraints
{
"type": "number",
"minimum": 0,
"maximum": 1,
"multipleOf": 0.01 // must be a multiple of 0.01
}enum
Restricts a value to a fixed set of allowed values:
{ "enum": ["pending", "active", "suspended", "deleted"] }additionalProperties
Controls whether keys not listed in properties are allowed. Set to false to reject any unexpected keys — useful for strict API contracts:
{ "additionalProperties": false }A Real-World JSON Schema Example
Here's a schema for a product in an e-commerce API — the kind you'd see in an OpenAPI definition or an internal data contract:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/schemas/product.json",
"title": "Product",
"description": "A product available for purchase",
"type": "object",
"required": ["id", "name", "price", "category"],
"properties": {
"id": {
"type": "integer",
"description": "Unique product identifier"
},
"name": {
"type": "string",
"minLength": 1,
"maxLength": 200
},
"price": {
"type": "number",
"minimum": 0,
"description": "Price in USD"
},
"category": {
"type": "string",
"enum": ["electronics", "clothing", "books", "home", "sports"]
},
"tags": {
"type": "array",
"items": { "type": "string" },
"uniqueItems": true
},
"dimensions": {
"type": "object",
"properties": {
"width": { "type": "number", "minimum": 0 },
"height": { "type": "number", "minimum": 0 },
"depth": { "type": "number", "minimum": 0 }
},
"required": ["width", "height", "depth"]
},
"inStock": { "type": "boolean" }
},
"additionalProperties": false
}How to Validate JSON Against a Schema
Picking a jsonschema validator? The standard choices are Ajv (JavaScript), jsonschema (Python), and online services like jsonschemavalidator.net. If you need the other direction — generating json schema from an example, or a json schema from json document — try GenSON in Python or transform.tools in the browser. These are json schema generator tools that infer a starting schema from a sample. Tighten required, additionalProperties, and value constraints by hand afterwards.
In JavaScript (Ajv)
Ajv is the most widely used JSON Schema validator in the JavaScript ecosystem. It supports Draft-07 and Draft 2020-12.
npm install ajvimport Ajv from 'ajv';
const ajv = new Ajv();
const schema = {
type: 'object',
required: ['id', 'name', 'email'],
properties: {
id: { type: 'integer' },
name: { type: 'string', minLength: 1 },
email: { type: 'string' },
},
additionalProperties: false,
};
const validate = ajv.compile(schema);
const data = { id: 42, name: 'Alice', email: 'alice@example.com' };
const valid = validate(data);
if (!valid) {
console.error(validate.errors);
} else {
console.log('Valid!');
}ajv.compile(schema) returns a reusable validate function. Compiling once and calling the result repeatedly is significantly faster than re-compiling on every call.
In JavaScript (browser, no dependencies)
For simple type and required-field checks without adding a dependency, you can validate manually — but for anything beyond trivial checks, use Ajv. Schema validation logic is complex enough that hand-rolling it is error-prone.
In Python (jsonschema)
pip install jsonschemaimport json
import jsonschema
from jsonschema import validate, ValidationError
schema = {
"type": "object",
"required": ["id", "name", "email"],
"properties": {
"id": {"type": "integer"},
"name": {"type": "string", "minLength": 1},
"email": {"type": "string"},
},
"additionalProperties": False,
}
data = {"id": 42, "name": "Alice", "email": "alice@example.com"}
try:
validate(instance=data, schema=schema)
print("Valid!")
except ValidationError as e:
print(f"Invalid: {e.message}")Validating a JSON file against a schema
import json
import jsonschema
with open('schema.json') as f:
schema = json.load(f)
with open('data.json') as f:
data = json.load(f)
jsonschema.validate(instance=data, schema=schema)In the browser (fixjson)
If you need to check that a JSON document is structurally valid before working with it — without writing any code — fixjson's JSON validator checks syntax and flags errors instantly. Paste your JSON and any syntax problems appear inline with the exact line and position of each error.
For full schema validation (checking types, required fields, constraints), use one of the library-based approaches above, or a dedicated schema testing tool like jsonschemavalidator.net.
$ref: Reusing Schemas
Real schemas don't define everything inline. The $ref keyword lets you reference another schema by its $id or by a JSON Pointer path within the same document, keeping schemas DRY and composable.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"address": {
"type": "object",
"required": ["street", "city", "country"],
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"country": { "type": "string", "minLength": 2, "maxLength": 2 }
}
}
},
"type": "object",
"properties": {
"billingAddress": { "$ref": "#/$defs/address" },
"shippingAddress": { "$ref": "#/$defs/address" }
}
}$defs is the standard place to define reusable sub-schemas within a single file (it replaced definitions in Draft 2019-09).
Combining Schemas: allOf, anyOf, oneOf
JSON Schema has three composition keywords for expressing "must match all of," "must match at least one of," and "must match exactly one of":
// allOf: must satisfy every schema listed
{
"allOf": [
{ "type": "object" },
{ "required": ["id"] },
{ "properties": { "id": { "type": "integer" } } }
]
}
// anyOf: must satisfy at least one schema
{
"anyOf": [
{ "type": "string" },
{ "type": "null" }
]
}
// oneOf: must satisfy exactly one schema (they must be mutually exclusive)
{
"oneOf": [
{ "properties": { "type": { "const": "circle" } }, "required": ["radius"] },
{ "properties": { "type": { "const": "rectangle" } }, "required": ["width", "height"] }
]
}The format Keyword: date-time, email, uri, uuid
Beyond raw types, JSON Schema's format keyword annotates strings with their intended shape — an ISO date-time, an email address, a URI, a UUID, an IP address, and so on. It looks like this:
{
"type": "object",
"properties": {
"id": { "type": "string", "format": "uuid" },
"created_at": { "type": "string", "format": "date-time" },
"email": { "type": "string", "format": "email" },
"homepage": { "type": "string", "format": "uri" }
}
} A subtle but important point: in Draft 2020-12 (and 2019-09), the format keyword is an annotation by default — it doesn't enforce the format unless the validator is configured to. In Draft-07 and earlier, it was assertion by default in some validators. To make it strict in Ajv:
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
const ajv = new Ajv();
addFormats(ajv); // registers date-time, email, uri, uuid, etc.
// In 2020-12 you must opt in to format assertion in your meta-schema or
// explicitly pass { validateFormats: true }, depending on Ajv version. Common format values: date-time, date, time,duration, email, idn-email, hostname,ipv4, ipv6, uri, uri-reference,uuid, regex, json-pointer.
Treat format as a hint, not a security boundary — a string that passes format: "email" is still untrusted input. For deliverability or true validity, verify out-of-band (DNS, send a confirmation email, run a regex you control).
Common JSON Schema Mistakes
Confusing "properties" with "required"
Listing a key under properties does not make it required. A key in properties defines the schema for that key if it appears. To mandate its presence, you must also list it in required.
Using "integer" when you mean "number"
"type": "integer" rejects 3.14. If your field can be any number (including decimals), use "type": "number".
Forgetting that "additionalProperties" defaults to true
By default, JSON Schema allows any additional properties not listed under properties. This is intentional — schemas are meant to be open by default, following Postel's Law. If you want a closed object, explicitly set "additionalProperties": false.
Not specifying $schema
Different drafts have different semantics. Omitting the $schema keyword leaves validators guessing which version to use. Always include it:
{ "$schema": "https://json-schema.org/draft/2020-12/schema" }Treating JSON Schema validation as security validation
JSON Schema validates structure and types — it is not a sanitization tool. A string that passes "type": "string" could still contain SQL injection, XSS payloads, or other malicious content. Schema validation is a first gate, not a complete security measure.
JSON Schema and OpenAPI
OpenAPI (formerly Swagger) uses a subset of JSON Schema to describe request bodies, response bodies, and parameters. If you've written an OpenAPI spec, you've written JSON Schema — though OpenAPI uses a modified dialect with some extensions and restrictions.
This means JSON Schema knowledge transfers directly to API design. The components/schemas section of an OpenAPI document is a collection of JSON Schemas, and tools like Stoplight, Redoc, and Swagger UI render them into interactive documentation automatically.
Working With Ajv in Practice
Two flags pay for themselves in any real project:
strict: true(default in modern Ajv) — rejects unknown keywords and unknown formats at schema-compile time, catching typos like"minimun"or a missing$schemabefore they silently let data through.allErrors: true— collect every validation failure instead of stopping at the first. Pairs well withajv.errorsText()for user-facing messages.
To generate fake JSON from a schema for tests and fixtures, use json-schema-faker: it walks the schema and produces values that match (types, ranges, enums, even format when paired with ajv-formats). Useful for property-based testing or seeding a UI from a schema.
Generating a JSON Schema from Existing JSON
If you have JSON data and want to generate a starting schema from it, several tools automate the process:
- quicktype.io — infers schemas (and type definitions for TypeScript, Go, Python, and more) from sample JSON
- generate-schema (npm) —
generate-schema json schema.json data.json - Python: genson —
pip install genson, thenSchemaBuilderinfers a schema from one or more sample objects
Generated schemas are a starting point, not a finished product. They infer types from sample data, but they don't know your business rules — they can't infer that a string field is an email address, or that an integer must be positive, or that two fields are mutually exclusive. Always review and tighten a generated schema before using it in production.
Frequently Asked Questions
What is JSON Schema used for?
Describing and validating the structure of JSON data — which keys are required, what type each value must be, and what values are allowed. It powers OpenAPI, IDE autocomplete for package.json/tsconfig.json, form generation, and data-pipeline contracts.
What's the difference between JSON and JSON Schema?
JSON is the data; JSON Schema is a separate JSON document that describes what valid data should look like. If you're new to the format itself, start with What Is JSON?
Which JSON Schema draft should I use?
Draft 2020-12 is current and recommended for new schemas; Draft-07 is still common because of wide tooling support. Always declare the version with $schema so validators don't guess.
How do I validate JSON against a schema?
Use Ajv in JavaScript, the jsonschema library in Python (examples above), or a web validator. To first confirm the JSON is even syntactically valid, see How to Validate JSON or run it through the validator.
Does JSON Schema validate format (email, date-time, uri…)?
In Draft 2020-12 / 2019-09 the format keyword is an annotation by default — validators only enforce it when configured to. With Ajv, register ajv-formats and enable format assertion to actually reject malformed values.
Conclusion
JSON Schema turns informal documentation ("this field should be a number") into a machine- checkable contract that validators can enforce consistently across languages, teams, and systems. The core vocabulary — type, properties, required, items, enum — covers the vast majority of real-world use cases. More advanced features like $ref, composition keywords, and format handle the rest.
Start with a minimal schema that enforces only what you genuinely need to enforce. Add constraints incrementally as your understanding of the data solidifies. A schema that rejects valid data is worse than no schema at all.
If the JSON you're validating has syntax errors before it even reaches the schema validator, fix those first — a schema validator requires syntactically correct JSON as input. Once the JSON is clean, validate it and then apply your schema.