JSON Schema Validation in 2026: AJV, Formats, and Where Validation Stops Helping
Validation is where JSON bugs go to die, and also where a class of silent ones is created by people who turn format on and assume it is enforced. Measured figures below; the rest is behaviour you can reproduce in ten minutes.
Adoption, pulled not remembered
Source: the npm downloads API. Window 2024-10-01 to 2024-10-30 against 2026-09-11 to 2026-10-10, pulled 2026-10-12.
| Package | First 30 days | Last 30 days | Change | Job |
|---|---|---|---|---|
ajv | 496.3M | 1.749B | +252% | JSON Schema validator |
ajv-formats | 105.3M | 617.6M | +487% | Optional format keywords for AJV |
jsonschema | 13.14M | 26.92M | +105% | Spec-compliance test suite (Python-origin) |
Two honest caveats. ajv-formats is the companion package to ajv, so its installs are mostly a subset of AJV's — adding the two rows counts the same adoption twice, which the libraries page warns about for metapackages generally. And the fastest-growing row here is the format add-on growing 1.9× faster than the validator itself, which says a lot of people are discovering the default does not check formats.
The default is stricter than you think and looser than you hope
import Ajv2020 from 'ajv/dist/2020.js';
const ajv = new Ajv2020({ allErrors: true }); // strict mode by default
const validate = ajv.compile(schema); // compiles to JS — this is why it is fast
if (!validate(data)) console.log(validate.errors); // instancePath, keyword, message
additionalPropertiesis not implied. A schema listing three properties accepts forty. Every "our API rejects unknown fields" claim needs the explicit keyword, and for objects whose keys are data ({"user-1": {...}}) you wantpropertyNames+additionalProperties: falseat the right nesting level.formatis annotation-only unless you opt in.{"type":"string","format":"email"}passesnot-an-emailin AJV's default configuration. Addajv-formats(fast, regex-based) orajv-formats-2019, orajv-formatswithaudit: trueif you want violations reported without failing. Python'sjsonschemabehaves the same way for the same reason: format validation is explicitly not required by the specification.type: "integer"accepts1.0in some dialects and not others, and JSON has no float type —1.0and1are the same number. If precision matters, it is a string in your schema.requiredchecks presence, not truthiness.{"name": ""}and{"email": null}are both valid under most hand-written schemas. Add"minLength": 1, or{"type": "string", "minLength": 1}— the null case needstypeto exclude it.
Draft choice, briefly
Draft 2020-12 is the current recommendation: $defs replaces definitions, prefixItems/items split, $ref siblings finally allowed. Draft-07 remains the most common thing you will meet in the wild and is fine for closed systems. OpenAPI 3.1 uses full JSON Schema; OpenAPI 3.0 uses a dialect subset that a validator does not know about, and its nullable keyword is not JSON Schema at all — this is the single largest source of "the schema validated but the payload was wrong" reports in API shops.
Validate-or-crash, in practice
| Where | Policy | Why |
|---|---|---|
| Ingress to your API | compile once at boot, reject with 400 + instancePath | the crash is the feature; compile-once keeps it microsecond-cheap |
| CI, config files | validate and exit non-zero | a bad config that starts is worse than a deploy that stops |
| Third-party webhook payloads | validate and log, keep processing | you do not control the producer; a hard fail means a partner outage you own |
| Internal, one producer, same repo | skip it | schema drift is caught by the type checker and the test suite |
The failure mode at boundaries is not "validation too strict" but validating after the transformation: JSON.parse has already destroyed a 19-digit ID (see hacks) and collapsed duplicate keys before any schema sees the object. No schema can express "this integer must survive a round-trip", because the damage is upstream of the format's type system.
Compiling once is the whole performance story
AJV compiles schemas to JavaScript; the cost is the compile, not the validation. Compiling per request is the mistake that gets "AJV is slow" posted online. allErrors: true is slower and usually worth it at an API boundary, because returning every field error in one response is better UX than four round-trips.
What this site does and does not do
The formatter validates syntax with strict JSON.parse — position-in-input errors and nothing else. Schema validation belongs in your runtime, not in a browser tab: a schema is a contract about a system, and a paste box has no idea what your system promises. Validate shapes here by eye, and ship ajv at the boundary where the bytes arrive.
See also: patterns and gotchas for the null/NaN/BOM class, and alternatives for formats that avoid the precision problems above entirely.