JSON Schema Validator
Check a JSON document against a JSON Schema and see every violation with its data path and schema path — entirely on this device.
Validates against JSON Schema drafts 06, 07, 2019-09 and 2020-12, entirely on this device. Draft-04 and older are not supported.
Paste a schema and a document — the result appears here.
Press ? for shortcuts
Do it offline:$ npx ajv-cli validate -s schema.json -d data.json
How to use it
- Put the schema in the left pane and the document in the right — type, paste, or drop a .json file on either. Below 64 KB the verdict follows your keystrokes; past 1 MB combined a button names the size it is about to check, and ⌘/Ctrl+Enter runs it.
- Leave the draft control on Auto to take the dialect from $schema, or pick one of the four to override the declaration and see how the same keywords behave elsewhere.
- Read the rail top to bottom. Each entry names the data path, the schema path and the keyword; Jump to error moves the cursor to that value whenever the path can be located in the text you pasted.
- Copy or download the report for a ticket, or Share a link that reopens both panes and the same three options. Turn off All violations when you only want the first thing that broke.
Paste a schema on the left and the document to check on the right. The rail lists every violation with the path into your data, the path into the schema that rejected it, and the keyword responsible — so a failure tells you which rule fired, not just that something is wrong. Drafts 06, 07, 2019-09 and 2020-12 are supported; draft-04 and older are not. Everything runs here; nothing is uploaded.
How it works
A JSON Schema is itself a JSON document, and validating is a two-part question: is the schema a legal schema, and does the instance satisfy it? Both parts run here in that order. The schema is first checked against its own meta-schema — the specification publishes one per draft, written in JSON Schema — so a misspelt type or a keyword given the wrong shape is reported as a broken schema before your document is looked at even once.
Applying a schema is a walk over the instance. At each position the applicable keywords are evaluated, and every failure carries two coordinates. The data path is an RFC 6901 JSON Pointer to the value that failed — /orders/2/total, or the empty pointer for the document as a whole. The schema path points the other way, into the schema document, naming the exact subschema and keyword that rejected it. Two entries with the same data path and different schema paths mean two separate rules turned the same value down.
Which value a failure is attached to is decided by which keyword failed, and that surprises people. additionalProperties is a rule about the object, so its failure is anchored at the object and the unexpected member name travels in the error parameters; required behaves the same way, one entry per missing member, all anchored at the object that lacks them. Keywords that apply subschemas — anyOf, oneOf, items — report the branch failures they saw and then a summary failure of their own, which is why one bad value can produce a short cascade rather than a single line.
Drafts differ in vocabulary, not in that machinery. 2019-09 moved reusable subschemas from definitions to $defs and added unevaluatedProperties; 2020-12 replaced the tuple form of items with prefixItems. A keyword a draft does not define is not an error — it carries no assertion at all, so a schema written for 2020-12 and validated as draft-07 will quietly accept documents it was meant to reject. Rather than let that pass in silence, this page lists such keywords as notes beside the verdict. format is the other draft-defined surprise: the specification makes it an annotation by default, so asserting it is a choice, exposed here as a switch that is on unless you turn it off.
Examples
config.schema.json
{
"type": "object",
"properties": {
"retries": { "type": "integer", "minimum": 5 }
},
"required": ["retries"],
"additionalProperties": false
}
config.json
{ "retries": 1, "verbose": true }Invalid — 2 violations
1. Data path: (whole document) · Schema path: #/additionalProperties · Keyword: additionalProperties
must NOT have additional properties
2. Data path: /retries · Schema path: #/properties/retries/minimum · Keyword: minimum
must be >= 5
Note: Schema has no "$schema" keyword, so it was validated as 2020-12.
Two rules, two coordinates. The stray verbose member is reported at the whole document, because additionalProperties constrains the object and the member name travels as a parameter — the second entry points straight at the value that was too small. No $schema was declared, so the note names the assumed dialect.
user.schema.json
{
"type": "object",
"required": ["id", "name", "email"],
"properties": {
"id": { "type": "integer" }
}
}
user.json
{}Invalid — 1 violation
1. Data path: (whole document) · Schema path: #/required · Keyword: required
must have required property 'id'
Note: Schema has no "$schema" keyword, so it was validated as 2020-12.
The same empty object with All violations turned off. Three members are missing and the run stops after the first, so the count is a property of the switch rather than of the document; leave the switch on and the rail lists id, name and email as three separate entries.
contact.schema.json
{
"type": "object",
"properties": {
"contact": { "type": "string", "format": "email" }
},
"required": ["contact"]
}
contact.json
{ "contact": "ana at example.com" }Invalid — 1 violation
1. Data path: /contact · Schema path: #/properties/contact/format · Keyword: format
must match format "email"
Note: Schema has no "$schema" keyword, so it was validated as 2020-12.
Assert formats is on, so email is checked and the missing @ is a failure. Switch it off and the identical pair comes back valid with a note saying the format keywords were skipped — the same document, a different question asked.
matrix.schema.json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "array",
"prefixItems": [{ "type": "string" }, { "type": "integer" }],
"contains": { "type": "boolean" },
"minContains": 2
}
matrix.json
["a", "b", true]Invalid — 4 violations
1. Data path: /1 · Schema path: #/prefixItems/1/type · Keyword: type
must be integer
2. Data path: /0 · Schema path: #/contains/type · Keyword: type
must be boolean
3. Data path: /1 · Schema path: #/contains/type · Keyword: type
must be boolean
4. Data path: (whole document) · Schema path: #/contains · Keyword: contains
must contain at least 2 valid item(s)
A 2020-12 vocabulary and the cascade an applicator keyword produces. Entry 1 is the positional rule rejecting "b"; entries 2 and 3 are contains reporting the members it tried and could not use; entry 4 is minContains stating the count it needed. Only one member — true — satisfied contains.
temperature.schema.json
{
"$schema": "http://json-schema.org/draft-06/schema#",
"type": "number",
"exclusiveMinimum": 0
}
temperature.json
0Invalid — 1 violation
1. Data path: (whole document) · Schema path: #/exclusiveMinimum · Keyword: exclusiveMinimum
must be > 0
A bare number is a legal instance, and the empty pointer is how a failure against it is addressed. draft-06 is where exclusiveMinimum became a number in its own right. No note here — the dialect was declared, not guessed.
tree.schema.json
{
"$defs": {
"node": {
"type": "object",
"properties": { "child": { "$ref": "#/$defs/node" } }
}
},
"$ref": "#/$defs/node"
}
tree.json
{ "child": { "child": {} } }Valid
Note: Schema has no "$schema" keyword, so it was validated as 2020-12.
A schema that refers to itself, which is ordinary for tree shapes and terminates because the document does. The reference resolves inside the same file, so nothing is fetched; a pointer to another file or a URL is refused instead, with the message quoted below.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
The empty pointer is the whole document
RFC 6901 addresses the root with an empty string, so a root failure arrives with nothing to print. The rail shows it as the whole document and Jump to error selects the entire instance. Any non-object instance — a bare string, number, true or null — is a legal document to check, and all of its failures land there.
One missing member, one entry
required does not stop at the first absence: each named member that is missing produces its own entry, all sharing the same data path and schema path and differing only in the member carried in the parameters. Turning All violations off collapses that list to whichever one was reached first.
A keyword from a newer draft is inert, not wrong
Validate a schema using prefixItems as draft-07 and nothing fails: the keyword is unknown there, so it asserts nothing and the document passes. That silence is the dangerous case, so it is reported as a note naming the keyword and the drafts in which this validator would have applied it. Unknown keywords of your own are treated the same way — noted, never fatal.
Draft-04's boolean exclusiveMinimum is a schema error
In draft-04, exclusiveMinimum was a boolean modifying minimum. From draft-06 it is the numeric limit itself, so the boolean form no longer satisfies the meta-schema and comes back as an invalid schema rather than as a data failure. Rewrite it as "exclusiveMinimum": <the old minimum> and drop the boolean.
format asserts only because you asked
The specification defines format as an annotation, with assertion an opt-in a validator may offer. Assert formats is on here, and switching it off is honest rather than lax — but a format this validator does not implement is skipped either way, and says so in a note instead of pretending the value was checked.
Parser warnings travel with the verdict
A byte-order mark, duplicate members, lone surrogates and integers beyond ±2⁵³ are things JSON permits and parsers disagree about. Each is surfaced as a warning while validation proceeds against the parsed value — for a large integer that means the rounded double, so a maximum near that magnitude can pass on a value your database would refuse.
Nothing is fetched, and there is a ceiling
Only references inside the pasted document are resolved: anything pointing at another file or a URL is refused with a message instead of a download, so bundle your definitions before pasting. Schemas above 1 MB and documents above 4 MB are refused whole rather than truncated, and both panes stay exactly as you left them.
Common errors
Unexpected '}' at line 1, column 18 — probable cause: trailing comma after the last property.- The schema pane has to hold strict JSON before anything can be meta-checked, and the position, the caret frame and the probable cause come from the same parser the rest of this site uses. Nothing about the document is reported while the schema is unreadable.
Unexpected 'a' at line 1, column 2 — probable cause: property names must be double-quoted.- The same diagnosis, raised for the right-hand pane instead. Which pane failed is carried as a field on the result rather than baked into the sentence, so the rail can mark the correct editor without parsing its own message.
Schema is not valid at /exclusiveMinimum: must be number- The meta-schema rejected the schema itself — here the draft-04 boolean spelling under a draft-06-or-later dialect. The location is a pointer into your schema, and the sentence after the colon is the validator's own; one entry appears per meta-failure.
Schema declares "$schema" as "http://json-schema.org/draft-04/schema#", which this validator does not run. Supported drafts are draft-06, draft-07, 2019-09 and 2020-12; set the draft control to one of them to validate against it anyway.- A named refusal rather than a silent downgrade, because draft-04 differs in ways that change verdicts. The escape hatch is in the sentence: override the draft and the schema is validated as that dialect, with the differences yours to judge.
Reference "https://example.com/schemas/user.json" at # points outside this schema document. Nothing is downloaded here: only references inside the same document are resolved — "#", "#/…" pointers and "#name" anchors.- Detected before the validator is even built, so no request is ever contemplated. Inline the target under $defs and point the reference at it, or run the bundling step your schema toolchain already has.
The schema is larger than the 1 MB limit for this tool. Nothing left this device — validate against a smaller schema.- A refusal, not a truncation: a partially read schema would produce confident and wrong verdicts. The document pane has its own 4 MB ceiling, and the command below runs the same check without either.
integer 9007199254740993 is beyond ±2^53 — double-based parsers like JSON.parse will round it; keep it as a string if exactness matters- A warning, and the verdict still stands — but it stands against the rounded value, since that is what the parser produced. Carry identifiers this large as strings and constrain them with pattern.
FAQ
Why is draft-04 not supported, and what do I do?
Its meta-schema and several keywords differ enough that running it as a later dialect would change verdicts, so it is refused by name instead. Two ways forward: set the draft control to draft-06 and read the meta-errors as a migration list — the boolean exclusiveMinimum and exclusiveMaximum forms are usually the whole of it — or keep draft-04 and validate it in your own toolchain.
What is the difference between the data path and the schema path?
One is a pointer into what you are checking, the other a pointer into the rules doing the checking. Read the first to find the value to fix and the second to find the constraint to argue with. When several entries share a data path, the schema paths are what tell them apart.
Why do I get fewer violations when All violations is off?
Because the run stops at the first failure it reaches. That is cheaper on very large documents and closer to what most libraries do by default, but the count means nothing then — one reported failure does not mean one failure exists.
Which draft is used when my schema has no $schema?
2020-12, and a note beside the verdict says so rather than leaving it implicit. Declaring $schema is worth the line: it tells every other tool the same thing, and it is what the Auto setting reads.
Can it follow a $ref to another file or a URL?
No, and that is deliberate — resolving a remote reference would mean sending your schema's neighbourhood somewhere. References within the pasted document, including recursive ones, resolve normally; anything else is refused with a message naming the reference.
Why did my format keyword not reject anything?
Either assertion is switched off, or the format is one this validator does not implement — both produce a note beside the verdict. A format that is unknown is skipped rather than failed, because a validator is not permitted to invent a meaning for it.
Does anything leave my browser?
No. Both panes are validated on this device and no reference is ever fetched. Panes that look credential-shaped are not kept between visits.
Keyboard shortcuts
- ⌘/Ctrl + Enter
- Run the check again, which is how a large paste starts.
- ⌘/Ctrl + S
- Download the validation report instead of saving the page.
- ⌘/Ctrl + C
- With nothing selected, puts the whole report on the clipboard.
- ⌘/Ctrl + Shift + C
- Take the report even while a selection is live.
- t
- Flip the theme, unless a text field currently has focus.
- ? or ⌘/Ctrl + /
- Bring up the list of shortcuts.
- Esc
- Dismiss the topmost overlay.