json-schema2020-12validationopenapischema

JSON Schema Draft 2020-12: What Changed and the New Keywords

·10 min read·JSON Schema

Why 2020-12 is the draft that matters

JSON Schema has gone through many drafts, and for years tooling lagged behind the spec. Draft 2020-12 is where the ecosystem converged: it's what modern validators default to, and — crucially — it's the dialect OpenAPI 3.1 aligned with, ending years of "OpenAPI's schema is *almost* JSON Schema" pain. If you're writing new schemas in 2026, target 2020-12. This guide covers what changed and the keywords people most often get wrong.

Declare the dialect at the top so validators pick the right rules:

json
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object" }

The big change: prefixItems replaces the old array keywords

The most breaking change from older drafts is how arrays and tuples work. Previously, items did double duty — an object meant "every element matches this," an array meant "positional tuple." That overloading was confusing, so 2020-12 split it:

  • `prefixItems` — an array of schemas for positional (tuple) validation.
  • `items` — now *only* the schema for elements beyond the prefix (what additionalItems used to do).
json
{
  "type": "array",
  "prefixItems": [
    { "type": "number" },
    { "type": "string" }
  ],
  "items": { "type": "boolean" }
}

This validates [1, "hi", true, false]: position 0 is a number, position 1 a string, and every element after follows items. additionalItems is gone — if you see it, the schema is pre-2020-12.

$dynamicRef and $dynamicAnchor

2020-12 replaced the confusing $recursiveRef/$recursiveAnchor with `$dynamicRef` and `$dynamicAnchor`. These enable *extensible recursion* — a base schema can reference a placeholder that a schema extending it fills in later. It's an advanced feature you mostly meet in schema libraries (like a generic "tree" schema specialized per use), but knowing the rename saves confusion when reading modern meta-schemas.

anyOf vs oneOf: the distinction people get wrong

These look similar and behave differently:

  • `anyOf` — valid if the data matches at least one subschema (logical OR). This is what you want the vast majority of the time.
  • `oneOf` — valid only if the data matches exactly one subschema (exclusive XOR). If two subschemas both match, oneOf *fails*.
json
{ "anyOf": [ { "type": "string" }, { "type": "number" } ] }

The classic oneOf trap: writing { "type": "string" } and { "maxLength": 5 } as alternatives — a 3-character string matches both, so oneOf rejects it. Unless you genuinely need mutual exclusivity, reach for anyOf.

if / then / else: conditional validation

Conditional schemas apply different rules based on the data. The pattern reads exactly as it looks:

json
{
  "type": "object",
  "properties": { "country": { "type": "string" }, "postalCode": { "type": "string" } },
  "if":   { "properties": { "country": { "const": "US" } } },
  "then": { "properties": { "postalCode": { "pattern": "^[0-9]{5}$" } } },
  "else": { "properties": { "postalCode": { "minLength": 3 } } }
}

If country is US, the postal code must be five digits; otherwise a looser rule applies. if never produces errors itself — it only decides whether then or else applies.

Writing and validating 2020-12 schemas

  1. Draft a realistic sample and generate a starting schema from it, then tighten it with required, enum, and conditionals.
  2. Validate data against your schema to confirm your anyOf/oneOf and if/then/else behave as intended — the oneOf overlap trap is easy to miss by eye.
  3. Format the schema so deeply nested prefixItems and $defs stay readable.

For fundamentals start with the JSON Schema Guide; for reuse patterns see JSON Schema $ref and definitions.

Frequently asked questions

Array validation. prefixItems now handles positional/tuple schemas and items applies only to elements after the prefix — replacing the overloaded items and the removed additionalItems from older drafts.

Use anyOf unless you specifically need exclusivity. anyOf passes if at least one subschema matches; oneOf passes only if exactly one matches and fails when two overlap, which causes surprising rejections.

Yes — OpenAPI 3.1 aligned its schema object with JSON Schema 2020-12, so a schema written for one works in the other, ending the earlier divergence between the two.

$dynamicRef and $dynamicAnchor replace the older $recursiveRef/$recursiveAnchor, providing clearer extensible-recursion semantics for advanced, reusable meta-schemas.

Try JSON Schema Validator

Validate data against a Draft 2020-12 schema and see every error explained.