JSON Schema Tutorial: How to Write & Validate JSON Schema
What JSON Schema is, how to write one from scratch, the keywords you'll actually use, and how to validate JSON against it — with a Python example and guidance for choosing a validator.
The first time bad data breaks production, it's rarely a syntax error — the JSON parses fine. It's the wrong shape: a missing email, a string where a number belongs, a status that isn't one of the values your code handles. JSON Schema is how you catch that at the boundary, before it spreads through the system. This guide takes you from zero to a working schema you can validate against.
What is JSON Schema?
JSON Schema is a vocabulary for describing the structure of JSON data — which fields must exist, what type each value should be, and what counts as valid. A schema is itself written in JSON, so the same tools that read your data can read its contract.
It answers a different question than a plain JSON validator. A JSON validator checks “is this valid JSON?” (syntax). JSON Schema checks “is this the right JSON?” (shape and constraints). You usually want both. JSON Schema shows up wherever data crosses a boundary: validating API request and response bodies, configuration files, form input, queue messages, and contract tests between services.
Your first JSON Schema, by example
Say every user object must have a name and an email, may have a non-negative age, and must have a role from a fixed list. Here is the schema that says exactly that:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": { "type": "string", "minLength": 1 },
"email": { "type": "string", "format": "email" },
"age": { "type": "integer", "minimum": 0 },
"role": { "type": "string", "enum": ["admin", "user", "guest"] }
},
"required": ["name", "email", "role"],
"additionalProperties": false
}
This document passes — every required field is present and correctly typed:
{ "name": "Ada", "email": "ada@example.com", "age": 36, "role": "admin" }
This one fails several checks:
{ "email": "not-an-email", "age": -1, "role": "owner", "extra": true }
nameis missing, butrequireddemands it.ageis-1, below theminimumof 0.roleis"owner", which isn't in theenum.extraisn't declared andadditionalProperties: falseforbids unknown keys.emailfails only when format assertion is enabled; the examples below enable it explicitly.
The keywords you'll actually use
JSON Schema has a large vocabulary, but a handful of keywords cover the vast majority of real schemas:
| Keyword | Applies to | What it does |
|---|---|---|
type | any | Constrains the JSON type: string, number, integer, boolean, object, array, null. |
properties | object | Defines a sub-schema for each named property. |
required | object | Lists properties that must be present. |
additionalProperties | object | Set to false to reject any property not listed in properties. |
items | array | The schema every array element must match. |
enum | any | Restricts the value to a fixed set of allowed options. |
format | string | Semantic hint: email, uri, date-time, uuid, ipv4… |
minimum / maximum | number | Inclusive numeric bounds. |
minLength / maxLength | string | String length bounds. |
pattern | string | A regular expression the string must match. |
$ref / $defs | any | Reuse a named sub-schema — for shared types or recursion. |
How to validate JSON against a schema (in code)
A schema needs a validator that supports its declared dialect and keywords. For JavaScript or another language, compare implementations in the JSON Schema tooling directory and inspect their Bowtie compliance reports. Check the tested version and dialect, then test your own schemas and representative data; a directory listing is not an endorsement.
In Python, use a draft-specific validator and iter_errors when you want every failure rather than the first one:
from jsonschema import Draft7Validator, FormatChecker
validator = Draft7Validator(schema, format_checker=FormatChecker())
errors = sorted(validator.iter_errors(data), key=lambda e: list(e.path))
for error in errors:
path = ".".join(str(part) for part in error.path) or "(root)"
print(f"{path}: {error.message}")
Python's iter_errors lets you report failures by path, so callers learn which values failed and why instead of receiving one “invalid” boolean.
Which JSON Schema draft should you use?
The $schema line declares which version of the specification you're writing against, and it changes how some keywords behave. The practical guidance:
- Draft-07 — the dialect used by the example schema and Python validator above.
- 2019-09 — introduced features including
$defs,unevaluatedProperties, anddependentSchemas. - 2020-12 — changed tuple validation to use
prefixItemsand replaced recursive references with dynamic references. Review schema compatibility when migrating from earlier dialects.
Choose the dialect your validator and downstream systems support, and declare it with $schema. Check each implementation's documentation for dialect selection and unsupported features.
One gotcha: format (like email) is annotation-only by default in modern JSON Schema. Python jsonschema requires an explicit FormatChecker to check supported formats. If you rely on format, confirm your validator enables it and supports the particular format you need.
Write and validate schemas without code
When I'm sketching a schema or sanity-checking a payload during a review, I don't reach for a script — I use a browser tool. These tools process schema operations in your browser (disclosure: I built these). Remove secrets before using online tools and see our privacy policy for information about analytics and third-party services:
- JSON Schema Validator — our browser interface currently uses Ajv to check common Draft-07 structural rules and show failures by path. This implementation disclosure is not a recommendation of Ajv or a claim of full compliance. The tool does not currently enforce
formatassertions; verify production schemas with an implementation selected for your requirements. - JSON Schema Builder — build a schema visually instead of hand-writing keywords.
- JSON to Schema — infer a starter schema from a sample, then tighten it (add
required, formats, andenums).
A common workflow: infer a starter schema from a sample, refine its constraints, then test it with your selected validator in CI using both valid and invalid examples. A single sample cannot establish every optional field, allowed type or business rule.
FAQs
What is JSON Schema used for?
Describing and validating the structure of JSON — which fields must exist, their types, and allowed values. It's used for API bodies, config files, form input, and message payloads to catch the wrong shape of data before it causes bugs.
How do I validate JSON against a schema?
Choose a validator that supports your schema's dialect and required keywords. Compare the official tooling directory, implementation documentation and compliance reports. The Python example above uses Draft7Validator.iter_errors to report failures by path, with explicit format checking.
What's the difference between JSON Schema drafts?
Drafts are specification versions. Draft-07 remains widely supported; 2019-09 introduced $defs and unevaluated keywords, while 2020-12 changed tuple validation to use prefixItems. Declare the dialect with $schema and choose one your validator supports.
How do I generate a JSON Schema from existing JSON?
Infer one from a sample with JSON to Schema, then refine: add required, formats, enums, and additionalProperties: false where the shape is fixed.
Is JSON Schema the same as a JSON validator?
No. A JSON validator checks syntax (is it valid JSON?); JSON Schema checks shape and constraints (is it the right JSON?).