# JSON Schema

## What it is

JSON Schema is a language for describing what a piece of JSON has to look like,
and it is written in JSON itself. A description in it, called a schema, sets out
the rules: this value is an object, it must have these properties, this one is a
whole number of at least one, that one a string of at most 200 characters. A
program called a validator takes a schema and an instance, the data being
checked, and answers whether the instance conforms, and if it does not, where
and why. The project writes its name with a capital S, JSON Schema, while a
single document written in the language is simply a schema, in lower case.

The specification is maintained by the JSON Schema project at json-schema.org.
The current version is 2020-12 and the one before it 2019-09; earlier versions
were numbered drafts, such as draft-04 and draft-07, and older tools and
articles still refer to them by those names. The text comes in two parts. The
core document defines how a schema is processed, how one schema refers to
another and the media type `application/schema+json`, and a second document, on
validation, defines the keywords that check values. The current documents for
2020-12 are both dated 16 June 2022.

Each keyword adds one constraint. `type` names one of six basic types, string,
number, boolean, array, object or null, or `integer`, which accepts only numbers
whose fractional part is zero. Numbers take `minimum`, `maximum` and
`multipleOf`; strings take `minLength`, `maxLength` and `pattern`, a regular
expression; arrays take `minItems`, `maxItems` and `uniqueItems`; objects take
`properties`, `required` and `additionalProperties`. Keywords such as `allOf`,
`anyOf`, `oneOf` and `not` combine schemas, and `$ref` points at a schema
defined elsewhere, often under `$defs` in the same document, so a shape written
once can be used in many places. Anything a schema does not mention is allowed.
The empty schema, `{}`, accepts every instance there is.

A schema can also say which version of the language it is written in. The
`$schema` keyword holds the address of a meta-schema, a schema that describes
valid schemas for that version, and the specification page notes that the
meta-schema validates itself. For 2020-12 that address is
`https://json-schema.org/draft/2020-12/schema`.

## Why it matters

JSON says nothing about meaning. Any well-formed document parses, whether or not
the program reading it can do anything with it. Without a written description,
every program that reads a document keeps its own idea of what valid looks like,
spread across conditions in its code, and those ideas drift apart until a
request fails in production. A schema puts the rules in one place, in a form
that people and programs can both read, and it can be stored, versioned,
reviewed and published like any other file.

The specification states its intent broadly: JSON Schema is meant for the
validation and documentation of JSON data, for hyperlink navigation and for
interaction control, and the validation vocabulary is also meant to give the
screens that show the data hints about it. In practice that means one
description can reject bad input at the edge of a service, document an API,
drive a form, and check that the JSON a language model returns has the fields
the next step expects.

It has also become the common ground that other specifications build on. OpenAPI
3.1 defines its Schema Object as a superset of JSON Schema 2020-12, so the
request and response bodies in an API description are JSON Schema with a few
additions. MCP, the protocol through which AI agents discover and call tools,
describes each tool's parameters with an `inputSchema` that is a JSON Schema,
and may describe its result with an `outputSchema` that clients should validate
structured results against. A description written once can therefore serve a
validator, an API document and an agent, and the three cannot disagree.

## What it looks like in practice

The schema below describes the input of a tool that books a delivery slot. It
names the version it is written in, lists four properties with their rules, says
which three must be present, and refuses any property it does not list:

```json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.com/tools/book-slot.json",
  "title": "Book a delivery slot",
  "type": "object",
  "properties": {
    "order": { "type": "string", "pattern": "^[0-9]{4,8}$" },
    "date": { "type": "string", "format": "date" },
    "pallets": { "type": "integer", "minimum": 1, "maximum": 30 },
    "note": { "type": ["string", "null"], "maxLength": 200 }
  },
  "required": ["order", "date", "pallets"],
  "additionalProperties": false
}
```

An instance that breaks the rules:

```json
{ "order": "4471", "date": "27/10/2026", "pallets": 0, "pallet": 2 }
```

The instance fails on two counts. `pallets` is below the minimum of 1, and
`pallet`, a typo for `pallets`, is a property the schema does not list. The
missing `note` is no fault, because `note` is not in `required`; had it been
sent as `null`, that would pass too, since its type allows null. The date is the
instructive one. The `date` format asks for the year first, and `27/10/2026`
puts the day first, but a validator running with its defaults accepts it,
because the specification makes `format` an annotation that is checked only when
that checking is switched on. Switch that checking on in a validator that checks
dates, and the same instance fails on three counts.

A tool described this way is what this site's page on
[turning existing software into tools for AI agents](/ai-tools/) is about. It
describes each operation an agent should have, such as booking a slot or
releasing an order, offered as a tool with typed inputs, errors an agent can act
on and idempotent calls, so that a retry never books twice. The description is
an OpenAPI document generated from the code, and an MCP server offers the same
operations. The page's argument is that an agent trusts a tool's description
even when it has gone out of date, and one written by hand is already out of
date a week after it ships. The descriptions there are therefore generated from
the code outward, through the database, the API, the description and the client,
with a check at every step that fails the build when two of them disagree.

## Where it goes wrong

**A listed property that is never required.** `properties` checks only the
properties that are present, so an object that leaves all of them out passes.
Presence is a separate keyword, and leaving `required` out means nothing is
required at all. Write `required` beside `properties` every time, and keep a
test case for each required property with that property removed.

**Typos that pass as extra properties.** Without `additionalProperties`, any
property the schema does not list is allowed, so `pallet` goes through and the
real `pallets` is quietly missing. Setting it to `false` closes the object, but
the keyword sees only the `properties` and `patternProperties` in its own schema
object. A schema that builds on another through `allOf` or `$ref` and then
closes itself this way rejects the very properties it inherited. Version 2020-12
has `unevaluatedProperties` for that case, since it also counts the properties
checked through `allOf`, `$ref` and the other keywords that apply schemas in
place. Use `additionalProperties: false` on a schema that stands alone and
`unevaluatedProperties: false` on one that is composed.

**`format` that checks nothing.** By default `format` is an annotation and not
an assertion: the specification requires a validator that can check formats to
keep that checking switched off unless it is asked for, and even when it is
switched on, a validator may let any format pass unchecked. OpenAPI 3.1 follows
the same rule, and adds that how well formats are checked varies between
implementations. Switch format checking on in the validator you use, prove it
with a test case that should fail, and where a format matters but cannot be
checked, add a `pattern` or a check in code.

**Patterns that match part of a value.** A `pattern` is not anchored, so
`[0-9]{4}` accepts `order 4471b` because four digits appear somewhere inside it;
the specification's own example is that `es` matches `expression`. Start the
pattern with `^` and end it with `$` when the whole value has to match. Patterns
follow the ECMA-262 dialect, and the specification recommends a small set of
constructs for patterns that should behave alike everywhere, so keep to simple
classes, quantifiers and anchors.

**Missing, null and empty treated as one.** A property that is absent, a
property set to `null` and an empty string are three different instances.
`"type": "string"` rejects `null`, and a property not listed in `required` may
be absent. A client that sends `null` to mean no value, against a schema written
for absence, fails on every such request. Decide per property what each of the
three means and write it down: `["string", "null"]` when null carries meaning,
absence from `required` when the property is optional, and `minLength: 1` when
an empty string is not a value.

**No `$schema`, or two versions mixed.** When the root of a schema has no
`$schema`, the specification leaves the behaviour to the implementation, and
each validator applies whichever version it defaults to. The versions differ in
places that matter. In 2019-09 an array given to `items` described a tuple; in
2020-12 that job belongs to `prefixItems`, and `items` took over the role of
`additionalItems`. Put `$schema` in every root, follow the release notes when
moving between versions, and validate every schema against its meta-schema in
CI.

## Further reading

- [JSON Schema Specification](https://json-schema.org/specification) — the
  project's index of the current version, its meta-schemas and the release notes
  for every earlier draft.
- [JSON Schema: A Media Type for Describing JSON Documents](https://json-schema.org/draft/2020-12/json-schema-core)
  — Core 2020-12: how schemas are processed, `$schema`, `$ref` and `$defs`, and
  the keywords that combine schemas.
- [JSON Schema Validation: A Vocabulary for Structural Validation of JSON](https://json-schema.org/draft/2020-12/json-schema-validation)
  — the keywords for each type of value, `required`, and the rules for `format`.
- [JSON Schema 2020-12 Release Notes](https://json-schema.org/draft/2020-12/release-notes)
  — what changed since 2019-09, with `prefixItems` and `$dynamicRef` side by
  side with what they replaced.
- [OpenAPI Specification v3.1.1](https://spec.openapis.org/oas/v3.1.1.html) —
  the Schema Object as a superset of JSON Schema 2020-12, and `format` as an
  annotation by default.
- [Model Context Protocol: Tools](https://modelcontextprotocol.io/specification/2025-06-18/server/tools)
  — how a tool declares its parameters and its structured output in JSON Schema.

<https://engineer.company/glossary/json-schema/>
