JSON Schema is a declarative language that describes the shape of JSON data. You write one schema that lists which fields exist, which are required, what types they hold and what values are allowed. Any conforming validator can check data against it and return consistent, machine-readable errors. You no longer need hand-written validation in every service.
This guide covers the current dialect, draft 2020-12. It goes through the core keywords, the boolean combinators (oneOf, anyOf, allOf, not) and reuse with $ref and $defs. It ends with a full schema, validation examples in JavaScript and Python, and how to generate a schema from existing data.
What JSON Schema is and why it exists
A JSON Schema is a JSON document. Its keywords set constraints on the data it describes. For example, a value must be a string, an object must have a name property, or an array's items must be numbers between 0 and 100. When a validator checks an instance (the data) against a schema, it returns success or a list of errors that name the constraints that failed.
You get one source of truth for validation. One schema can check an HTTP API's request body, drive form validation in the browser, generate documentation, produce fake data for tests and scaffold TypeScript types. You write the rules once instead of copying fragile if checks across a codebase.
- API contracts. Reject malformed request and response payloads before they reach business logic.
- Configuration files. Validate a service config at startup and fail early with clear messages.
- Data pipelines. Check records before they enter a store or queue, so bad data never lands.
- Tooling. Editors like VS Code use JSON Schema for autocomplete and inline errors in JSON and YAML files.
Draft 2020-12 and the $schema keyword
JSON Schema has versions called drafts. The current, widely supported one is draft 2020-12. Older documents often use draft-07. The core keywords are nearly the same, but 2020-12 changed how arrays and references work in a few important ways. Always declare your dialect with the $schema keyword so validators read keywords correctly.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/schemas/user.json",
"title": "User",
"type": "object"
}$id gives the schema a base URI. Other schemas point at it with $ref, and relative references inside the document resolve against it. title and description are annotations. They don't constrain data, but tools show them in docs and error messages, so they are worth writing.
Core JSON Schema keywords
Most schemas use a small set of keywords, grouped here by the type of value they apply to.
Type and object structure
| Keyword | Applies to | What it does |
|---|---|---|
| type | any | Restricts the JSON type: string, number, integer, boolean, object, array or null. Can be an array of types. |
| properties | object | Maps property names to subschemas that their values must satisfy. |
| required | object | Lists property names that must be present (independent of properties). |
| additionalProperties | object | Controls properties not named in properties. false forbids them, and a schema constrains them. |
| items | array | Schema every array element must match. |
| enum | any | The value must equal one of a fixed list of allowed values. |
| const | any | The value must equal exactly this one value. Same as an enum with one item. |
Use const to pin a field to a single value, such as "version": { "const": 2 } or a type tag like "kind": { "const": "card" }. The comparison is by JSON value, so { "const": 1 } accepts 1 and 1.0 but rejects "1". const is also how you build a discriminator for oneOf, shown below.
String, number and array constraints
| Keyword | Applies to | What it does |
|---|---|---|
| minLength / maxLength | string | Bounds on string length in characters. |
| pattern | string | A regular expression the string must match (unanchored by default). |
| format | string | Semantic hint like email, date-time, uri or uuid. Enforced only if the validator opts in. |
| minimum / maximum | number | Inclusive numeric bounds. Use exclusiveMinimum / exclusiveMaximum for strict bounds. |
| multipleOf | number | The number must be an exact multiple of this value. |
| minItems / maxItems | array | Bounds on array length. |
| uniqueItems | array | When true, all array elements must be distinct. |
Combine schemas with oneOf, anyOf, allOf and not
The four combinators apply boolean logic to subschemas. They let JSON Schema model unions, mixins and mutual exclusion.
allOf: the data must match every subschema. Use it to add constraints to a base schema, a form of inheritance.anyOf: the data must match at least one subschema. Good for loose unions where overlap is fine.oneOf: the data must match exactly one subschema. Use it when the options are mutually exclusive, like a discriminated union.not: the data must not match the subschema. Useful for exclusions, such as "any string except these reserved words."
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"oneOf": [
{
"properties": {
"kind": { "const": "card" },
"cardNumber": { "type": "string", "pattern": "^[0-9]{16}$" }
},
"required": ["kind", "cardNumber"]
},
{
"properties": {
"kind": { "const": "bank" },
"iban": { "type": "string" }
},
"required": ["kind", "iban"]
}
]
}Reuse with $ref and $defs
Schemas often repeat structures, such as an address, a timestamp or a money amount. Instead of copying them, declare each once under $defs and point to it with $ref. A $ref is a URI reference. The fragment #/$defs/address is a JSON Pointer into the current document.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/order.json",
"type": "object",
"properties": {
"billingAddress": { "$ref": "#/$defs/address" },
"shippingAddress": { "$ref": "#/$defs/address" }
},
"required": ["billingAddress"],
"$defs": {
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"postalCode": { "type": "string" }
},
"required": ["street", "city"]
}
}
}$defs is the standard place for reusable subschemas. It replaced the older definitions keyword. References can also point to other documents. "$ref": "https://example.com/address.json" loads a schema by its $id. Recursive structures like trees work too, since a $ref can point back to the schema that contains it.
A full schema and validation example
Here is a complete user schema that uses most of the keywords above, followed by validation in JavaScript and Python.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/user.schema.json",
"title": "User",
"type": "object",
"properties": {
"id": { "type": "string", "format": "uuid" },
"email": { "type": "string", "format": "email" },
"age": { "type": "integer", "minimum": 0, "maximum": 150 },
"role": { "enum": ["admin", "editor", "viewer"] },
"tags": {
"type": "array",
"items": { "type": "string" },
"uniqueItems": true
}
},
"required": ["id", "email", "role"],
"additionalProperties": false
}In JavaScript, Ajv is the standard validator. Note the ajv-formats plugin. Without it, format is ignored.
import Ajv from "ajv/dist/2020.js";
import addFormats from "ajv-formats";
import schema from "./user.schema.json" with { type: "json" };
const ajv = new Ajv({ allErrors: true });
addFormats(ajv);
const validate = ajv.compile(schema);
const user = {
id: "7d4e0f1a-1b2c-4d3e-8f90-abcdef123456",
email: "dev@example.com",
role: "editor",
tags: ["beta"],
};
if (validate(user)) {
console.log("valid");
} else {
console.log(validate.errors);
}import json
from jsonschema import Draft202012Validator
with open("user.schema.json") as f:
schema = json.load(f)
validator = Draft202012Validator(schema)
user = {
"id": "7d4e0f1a-1b2c-4d3e-8f90-abcdef123456",
"email": "dev@example.com",
"role": "editor",
}
errors = sorted(validator.iter_errors(user), key=lambda e: e.path)
for error in errors:
print(f"{list(error.path)}: {error.message}")
if not errors:
print("valid")additionalProperties is false, so an unexpected field like isAdmin: true produces a clear error instead of passing quietly. That is what you want when you protect an API against overposting.
Generate a JSON Schema from JSON
Writing a schema by hand for a large payload is tedious. It is faster to generate a JSON Schema from a representative JSON sample and then tighten it. Paste your JSON into the generator below. It returns a draft 2020-12 schema with types, properties and required fields filled in.
Treat the output as a starting point, not a finished contract. An inferred schema is only as good as its sample. It can't know that age should be capped at 150, that role is an enum or that additionalProperties should be false. Review it and add the constraints one example can't show, such as bounds, patterns, enums and formats. If your JSON is messy, tidy it with the JSON Formatter and check that it parses with the JSON Validator first.