What Is JSON Schema? Drafts, Keywords and Validation

💡 10K searches/mo💰 CPC: $1.5⏱️ 7 min read

JSON Schema answers a deceptively simple question: "is this JSON the JSON we agreed on?" A schema is itself just JSON — a handful of keywords describing shapes, types, and limits — yet it powers API validation, config checking, form generation, and documentation across most of the industry. Here's the mental model, the vocabulary that matters, and the draft-version traps that bite in production.

Advertisement

The core idea in one example

Take a user object. A schema for it might read:

{"type":"object","properties":{"name":{"type":"string","minLength":1},"age":{"type":"integer","minimum":18},"email":{"type":"string"}},"required":["name","email"],"additionalProperties":false}

Read it aloud: it's an object; if name appears it's a non-empty string; age, if present, is an integer of at least 18; name and email must be present; nothing else is allowed. Feed that schema and any document to a validator and you get a verdict — plus, when it fails, a list of exactly where and why. That's the whole discipline. Everything else is vocabulary.

The keywords that do 95% of the work

KeywordWhat it enforcesExample
typeThe JSON type: object, array, string, number, integer, boolean, null{"type":"integer"}
propertiesPer-key subschemas for object members{"properties":{"age":{"minimum":0}}}
requiredKeys that must exist (any value){"required":["id"]}
itemsSchema every array element must match{"items":{"type":"string"}}
enumExact allowed values{"enum":["red","green"]}
minimum / maximumNumeric bounds (inclusive){"minimum":18}
minLength / maxLengthString length bounds{"maxLength":280}
patternECMAScript regex the string must match{"pattern":"^\\\\d{5}$"}
formatWell-known shape hint (email, date, uuid){"format":"date-time"}
additionalPropertiesForbid or constrain stray keys{"additionalProperties":false}

Two composition keywords deserve special mention because they replace hand-written conditional logic. anyOf accepts a document matching any listed subschema (a value that's either a string or null — the classic nullable-field problem). oneOf demands exactly one match, which is stricter and a common source of accidental failures when two branches overlap. allOf merges constraints and is the standard way to compose a base schema with per-endpoint extras.

required ≠ properties, and other first-week traps

Drafts: the versioning story that actually matters

JSON Schema has no releases in the npm sense; it has dialects, declared via $schema. The three you'll meet:

The practical damage shows up at API boundaries: a 3.0-era schema dropped into a 3.1 toolchain can misfire on $ref composition or items handling, and the error surfaces as a validation anomaly rather than a version complaint. When something validates in one tool and not another, $schema is the first thing to check.

How validators report failures

A good validator doesn't say "invalid." It says where and why, using three fields per error: an instance path (JSON Pointer into your document, like /items/2/price), a schema path (which keyword fired, like #/properties/price/minimum), and a message with keyword params. Ajv — the library behind most Node.js validation stacks — compiles your schema to a JavaScript function for speed and returns exactly this structure. Turn on allErrors or you'll fix problems one render at a time.

Try it on your own schema

Paste a schema and a document, pick a dialect, and see every violation with its pointer path — all in your browser.

Open the JSON Schema Validator →

Where schemas come from

Writing schemas by hand is fine for small contracts. For bigger surfaces, generate a starting point and edit: many tools infer a schema from sample documents, database rows, or TypeScript types. The result won't be tight — generators err permissive — but it gives you the property skeleton, and you add required, bounds, and enums where the business rules live. Once the schema exists, keep it in the repo next to the code it guards and let CI validate your fixtures against it, so drift gets caught at pull-request time.

Working with schemas day to day pairs naturally with the rest of the JSON toolchain: our JSON Schema validator runs the checks, the JSON formatter untangles minified payloads while you debug, and the YAML validator handles the config files that need the same discipline in YAML form. Converting tabular exports to test fixtures? CSV to JSON closes that loop.

Advertisement

Frequently Asked Questions

What is JSON Schema used for?

Four jobs, mostly: validating API request and response bodies before your code trusts them, documenting payload shapes in one machine-readable place, generating forms and client code from the contract, and guarding config files at deploy time instead of discovering a typo in production. One schema can serve all four, which is why it beats ad-hoc validation functions that drift from the docs.

Is a JSON Schema itself JSON?

Yes — a schema is a JSON document describing other JSON documents. That means you can store it next to your code, serve it from an endpoint, diff it in code review, and validate it against the spec's own meta-schema to catch typos like reqired before they do damage.

Which draft should I use?

Follow your ecosystem. OpenAPI 3.0 is built on draft-07, OpenAPI 3.1 on 2020-12, and Ajv 8 supports draft-07, 2019-09, and 2020-12 out of the box. If nothing forces your hand, draft-07 remains the safest interop choice because every tool understands it; pick 2020-12 when you want prefixItems, unevaluatedProperties, or full $ref composition.

What's the difference between required and properties?

properties declares what a key must look like if it's present; required lists keys that must be present. A field can appear in required without appearing in properties — it's then mandatory but unconstrained. To demand a field and constrain it, list it in both. This split trips up nearly everyone writing their first schema.

Why did my exclusiveMinimum stop working between drafts?

Draft-04 used booleans alongside minimum (exclusiveMinimum: true). Draft-06 and later replaced that with a number (exclusiveMinimum: 18), dropping the companion keyword. Ajv 8 rejects the boolean form as invalid for modern dialects, so a schema migrated between eras can fail compilation with a confusing message — the fix is rewriting it as the numeric form.

Related Tools