What Is JSON Schema? A Practical Tour
JSON Schema describes the shape a document must have — types, required fields, patterns — written in JSON itself. A tour of the keywords that matter.
JSON.parse answers one question: is this text well-formed JSON? A second, harder question shows up everywhere real systems meet: is this JSON the shape my code expects? JSON Schema is the standard answer — a vocabulary for describing JSON, written in JSON.
A schema, annotated
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "User",
"type": "object",
"required": ["id", "email"],
"properties": {
"id": { "type": "integer", "minimum": 1 },
"email": { "type": "string", "format": "email" },
"name": { "type": "string", "minLength": 1, "maxLength": 100 },
"role": { "enum": ["admin", "member", "guest"], "default": "member" },
"tags": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
"age": { "type": ["integer", "null"], "minimum": 0 }
},
"additionalProperties": false
}
Read top to bottom it says: this document must be an object; id and email must be present; id is a positive integer; email is a string that looks like an email; name is optional but bounded; role is one of three values; tags is an array of unique strings; age may be an integer or null; and no keys beyond the listed ones are allowed (additionalProperties: false — the flag that catches typos like "emial").
Against that schema:
{ "id": 7, "email": "a@b.co", "role": "owner" }
fails for exactly one reason: "owner" isn’t in the enum — everything else checks out. Remove id and it fails a second way: required violated. Syntax-valid, schema-invalid.
The keywords that do 90% of the work
| Keyword | Applies to | Meaning |
|---|---|---|
type |
any | object, array, string, number, integer, boolean, null — or a list of them |
properties / required |
object | per-key schemas / mandatory keys |
additionalProperties |
object | schema (or false) for keys not in properties |
items / minItems / uniqueItems |
array | per-element schema, length bounds, uniqueness |
minLength / maxLength / pattern |
string | length bounds / regex (ECMA flavor) |
minimum / maximum / multipleOf |
number | numeric bounds |
enum / const |
any | whitelist of values / exactly one value |
format |
string | advisory annotation: email, date-time, uri, ipv4… |
$ref / $defs |
any | reuse a subschema by pointer: { "$ref": "#/$defs/address" } |
allOf / anyOf / oneOf / not |
any | composition: must match all / at least one / exactly one / none |
if/then/else |
any | conditional rules (“if country is US, zip is required”) |
format deserves a footnote: in most validators it’s annotation-only unless you enable a format plugin — "email": "not-an-email" can pass. Don’t rely on it for security checks.
Drafts — why version numbers differ
JSON Schema is versioned by “drafts”: draft-04 (2013), draft-06/07 (2017–18, the long-lived classic), 2019-09 and 2020-12 (current). What changed in practice:
definitions→$defs(2020-12),$refbecame a normal keyword that can sit beside siblingsitemsas array-of-schemas →prefixItems; plainitemsnow means “all remaining elements”dependencies→ split intodependentRequired/dependentSchemasnullable: true(an OpenAPI-ism, never in core JSON Schema) →type: ["integer", "null"]
OpenAPI 3.0 schemas are draft-04-ish with nullable; OpenAPI 3.1 is real 2020-12 JSON Schema — a common source of confusion when migrating specs.
Where it fits in a stack
- API contracts: OpenAPI describes every request/response body as a JSON Schema object.
- Config files:
tsconfig.json,composer.json, GitHub Actions workflows all ship public schemas — that’s where your editor’s autocomplete and red squiggles come from (via schemastore.org). - Runtime validation: Ajv (JS),
jsonschema(Python),sanity/…validators in Go/Java check incoming payloads against the schema before business code runs. - Codegen: the same schema produces types, forms, docs and test fixtures — one source of truth for shape.
The boundary to remember
Syntax validation (what this tool does) is binary and instant: the text parses or it doesn’t, at a specific character. Schema validation is a second pass over the parsed value, checking rules a grammar can’t express — ranges, key presence, string patterns. A healthy pipeline does both: parse, then validate. If your JSON won’t even parse, fix that first with the formatter; if it parses but the API still rejects it, the error guide won’t help — you need the API’s schema.
Frequently asked questions
Does this site's validator check JSON Schema?
No — the formatter validates syntax (is this well-formed JSON?). Schema validation is a second layer: "is this well-formed JSON the right shape?" A document like {"age": "thirty"} is perfect syntax and a failed schema. Schema validation needs a validator library (Ajv in JavaScript, jsonschema in Python) that reads both the schema and the data.
Which draft of JSON Schema should I use?
Draft 2020-12 for anything new — it's the current stable release and what OpenAPI 3.1 aligns with. Draft-07 still dominates in the wild (Ajv defaults to it; OpenAPI 3.0 is draft-04-flavored). The practical differences are $defs replacing definitions and the bundled $ref semantics; for everyday schemas the keywords you actually write barely changed.
What's the difference between properties and required?
properties says "if this key appears, here's its shape" — it doesn't make the key mandatory. required is the separate list of keys that must be present. An object matching properties: {a: …} with empty required can omit a entirely. Missing-required is the #1 schema surprise for newcomers.
Can a schema generate documentation or code, not just validate?
Yes — that's half its value. Tools generate TypeScript/Python types (json-schema-to-typescript, quicktype), HTML forms (react-jsonschema-form), fake test data, and human-readable docs from the same schema file. OpenAPI builds its entire request/response contract on JSON Schema objects.