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.

Published 2026-09-24

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), $ref became a normal keyword that can sit beside siblings
  • items as array-of-schemas → prefixItems; plain items now means “all remaining elements”
  • dependencies → split into dependentRequired / dependentSchemas
  • nullable: 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.