Quick answer
Validating {"age": -3} against a schema that requires email and sets "minimum": 0 on age gives 2 errors: /age fails minimum and (root) fails required. The validator applies drafts 2020-12, 2019-09, 07 and 04, chosen from the $schema keyword, and runs entirely in your browser.
About the JSON Schema Validator
This tool checks a JSON document against a JSON Schema and tells you, location by location, what does not match. It understands the four drafts still in everyday use (2020-12, 2019-09, draft-07 and draft-04) and updates as you type: a plain valid or invalid, a table of errors with each location written as a JSON Pointer, and a short record of the checks that produced the verdict, including which draft's rules were applied and why.
Everything runs in your browser. The schema and the data are never uploaded, never stored and never written into the page address, so you can paste a payload with customer records or internal field names and it stays on your machine. If you do not have a schema yet, the JSON Schema generator drafts one from a sample document, and you can bring the result straight back here to test it.
How to use it
Paste your schema into JSON Schema. It has to be JSON; if yours is written in YAML, convert it first with the YAML to JSON converter. Then paste the data you want to check into JSON to validate. That box takes any JSON value, so a bare string must keep its quotes: "hello" is a string, while hello on its own is a syntax error.
Leave Draft on Auto to let the $schema keyword decide the rules, or pick a draft to override it. Overriding helps when a schema has no $schema line, or when you want to see whether it still means the same thing under a newer draft. If either box holds broken JSON, that box is flagged with the parser's own message, which in most browsers includes the line and column where parsing stopped.
Read the error table from the top. The first rows are the specific failures: a number below its minimum, a missing required key, a string that does not fit its pattern. Summary rows, which only say that an enclosing object or array contained a failure, are folded away underneath. Errors as JSON gives you the same list to paste into a bug report or a test. The copy-link button keeps only the draft setting, never the text you pasted.
Use cases
- Debugging a request a server rejects. Paste the endpoint's request schema and the body you sent; the pointer names the exact field to fix instead of a bare "400 Bad Request".
- Checking a configuration file before a deploy. Plenty of tools publish a schema for their config format. Validate a change locally before a pipeline run finds the typo for you.
- Writing or reviewing a schema. Paste a known-good sample and a few known-bad ones to confirm the schema accepts and rejects what you meant, especially around
required,additionalPropertiesandpattern. - Testing structured output. When a webhook, a queue message or a language model's tool call must follow a contract, check real samples against it and see every mismatch at once rather than only the first.
- Comparing drafts. Switch the menu to see whether a draft-07 schema changes meaning under 2020-12. The
$refexample further down is the classic case.
How the validator works
A JSON Schema is a set of keywords, each a rule about the value it sits next to. type says what kind of value is allowed, required lists keys an object must have, minimum and maximum bound numbers, pattern constrains strings with a regular expression, and properties, items, allOf, anyOf, oneOf and $ref apply further schemas to parts of the data. Validation walks the data and the schema together. A keyword that does not fit the current value's type, such as minLength on a number, is simply skipped.
The exact rules depend on the draft. With the menu on Auto, the validator reads $schema: a URI containing 2020-12, 2019-09, draft-07 or draft-04 selects that draft. A draft-06 URI is checked with the draft-07 rules, and a schema with no $schema at all gets 2020-12. The checks panel under the results always says which draft was used and where that choice came from.
Many validators stop at the first failure. This one evaluates every keyword, so a single pass shows every problem. The price is repetition: when /items/1/qty is below its minimum, the properties rule for item 1 fails, the items rule for the whole array fails, and the properties rule at the top of the schema fails as well. All of those are true, but only the first tells you what to change. That is why the deepest errors, the ones with the longest schema path, are listed first, and why any row that merely contains another failure is marked as a summary and tucked into a collapsed list.
A few more clean-ups happen before display. An additionalProperties failure reported for a key that properties already declares is dropped, because the specification says additionalProperties only covers keys that properties and patternProperties do not. The same goes for an unevaluatedProperties failure on a declared key that has already failed its own rule: once a declaration fails, the validator checks that key a second time, which would show one mistake as two or three rows. A "false schema" failure is merged into the keyword that holds the false, so you read one row saying a property is not allowed instead of two cryptic ones. The checks panel reports how many raw rows were merged or removed.
Rows under contains get special treatment when minContains is set. Against {"contains": {"const": 1}, "minContains": 2}, the array [1, 3] gives one specific error, "Only 1 item matches the contains schema; minContains requires at least 2." The row for the 3 at /1 moves to the summaries: that item is not wrong in itself, it simply does not count toward the two that are required.
Worked example: the sample order
The form opens with a small order schema and an order that breaks it. The validator reports 14 raw errors. After 5 are merged or removed as duplicates, 9 rows remain: 4 specific errors and 5 summaries. These are the specific ones, exactly as the tool lists them:
| # | Location | Keyword | Message |
|---|---|---|---|
| 1 | /items/1/sku | pattern | "tee-12" does not match the pattern ^[A-Z]{3}-[0-9]{4}$. |
| 2 | /items/1/qty | minimum | 0 is less than 1. |
| 3 | /email | format | "sam@shop..test" is not a valid email address (format "email"). |
| 4 | /coupon | additionalProperties | Property "coupon" is not allowed: additionalProperties is false. |
Read each row as "at this location, this keyword failed, because of this". /items/1/sku is the sku of the second item, since array positions count from zero; "tee-12" fails the pattern because its letters are lowercase and it has two digits, not four. The quantity next to it is 0 against a minimum of 1. The email has an empty label between its two dots, and coupon is not declared under properties, so additionalProperties: false rejects it. Fix those four values and every summary row disappears with them.
Worked example: alternatives with anyOf and oneOf
The schema {"anyOf": [{"type": "string"}, {"type": "integer", "minimum": 5}]} accepts a string or an integer of at least 5. Validating the number 3 produces these rows:
- (root) ·
type: Instance type "number" is invalid. Expected "string". - (root) ·
minimum: 3 is less than 5. - (root) ·
anyOf: Instance does not match any subschemas.
Here the anyOf row is the verdict and the other two explain why each alternative turned 3 down. You only need to satisfy one alternative, so the fix is to send a string or a larger integer, not to do both. oneOf works the same way but also fails when more than one alternative matches. Against {"oneOf": [{"type": "number"}, {"type": "integer"}]}, the number 3 gives: Instance does not match exactly one subschema (2 matches).
Worked example: $ref in draft-07 versus 2020-12
This draft-07 schema puts minimum right next to a $ref:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"qty": {
"type": "integer"
}
},
"properties": {
"qty": {
"$ref": "#/definitions/qty",
"minimum": 1
}
}
}
With the menu on Auto, {"qty": 0} comes back valid: in draft-07 and draft-04, a $ref replaces the whole object it sits in, so minimum is ignored. Switch the menu to Draft 2020-12 and the same input is invalid, with the message "0 is less than 1.", because from 2019-09 onward $ref is an ordinary keyword and its siblings apply. If an older schema depends on a keyword beside $ref, wrap both in allOf so it behaves the same under every draft.
The regex rule: nested and chained repeats are refused
pattern and patternProperties take regular expressions, and the regex engines in browsers and servers backtrack. A pattern such as ^(a+)+$ looks harmless, but when a string almost matches, the engine tries every way of sharing the letters between the inner and outer +, and the work roughly doubles with each extra character. A few dozen characters can lock up a tab or a server for minutes. This is catastrophic backtracking, known as ReDoS when someone triggers it on purpose.
So before anything runs, every pattern and every patternProperties key is scanned, and four shapes are refused: a repeated group whose body itself contains a quantifier or an alternation; a chain of three or more repeated parts that can match the same characters, such as ^\d*\d*\d*$ or .*foo.*bar.*, where a near-miss can be split between the parts in a number of ways that grows with a power of the string length; any backreference; and any pattern longer than 256 characters. Parts that cannot match the same characters do not form a chain, so ^\s*\w+\s*$ (spaces, then word characters, then spaces) is allowed, and so is a pair such as ^\d*\d*$. Inside such a group an optional ? counts as a quantifier too, because (a?a?)+ can split a run of letters in exponentially many ways; only the ? that opens a group such as (?: or (?=, or makes a quantifier lazy as in +?, does not count. A group that is only optional, like (a+)?, passes, because a ? after the group cannot repeat it. Here is the verdict for a few patterns, produced by the same check the validator runs:
| Pattern | Verdict |
|---|---|
^(a+)+$ | Refused: nests quantifiers (catastrophic backtracking) |
(x|xx)* | Refused: nests quantifiers (catastrophic backtracking) |
^(\w+\s?)+$ | Refused: nests quantifiers (catastrophic backtracking) |
^(a?a?)+$ | Refused: nests quantifiers (catastrophic backtracking) |
^(?:\d?\d?\d?)+x$ | Refused: nests quantifiers (catastrophic backtracking) |
(\w?)+ | Refused: nests quantifiers (catastrophic backtracking) |
(\w)\1 | Refused: uses a backreference |
^\d*\d*\d*$ | Refused: chains three or more repeated parts that can match the same characters (like \d*\d*\d*), which backtracks catastrophically |
.*foo.*bar.* | Refused: chains three or more repeated parts that can match the same characters (like \d*\d*\d*), which backtracks catastrophically |
^(a+)?$ | Allowed |
^(https?|ftp)://[^\s]+$ | Allowed |
^[A-Z]{3}-[0-9]{4}$ | Allowed |
^\s*\w+\s*$ | Allowed |
[ab]* | Allowed |
The check judges shape, not measured risk, so it errs in both directions. It turns away some patterns that would in practice be safe, and it lets through some slower ones: a pair of repeats over the same characters, such as ^\d*\d*$, still does work that grows with the square of the string length, so a string of many thousands of characters can take a noticeable moment. A pattern already matches anywhere in the string, so leading and trailing .* can simply be dropped: .*foo.*bar.* matches the same strings as foo.*bar. Most refused patterns can be flattened without changing what they match: (x|xx)* accepts exactly the strings x* does, (a|b)* the same as [ab]*, ^(a?a?)+$ the same as ^a*$, and ^(?:\d?\d?\d?)+x$ the same as ^\d*x$. Others can be loosened slightly: ^(\w+\s?)+$ becomes ^[\w\s]+$, which also allows doubled, leading and whitespace-only input. Patterns are compiled in Unicode mode as well, so a needless escape such as \- outside square brackets is reported as an error instead of being quietly accepted. A pattern that does not compile is named with its location in the schema, and if the data never reaches it, a note still points it out.
References, formats and privacy
$ref. References inside the pasted schema work: #/$defs/address, #/definitions/address in older drafts, and references to a subschema by its own $id. References to another document, whether an https URL or a relative file name like address.json, are never fetched. When the data reaches one, the check stops and suggests copying the referenced schema into $defs. When it does not, the result still appears, with a note naming the reference: a schema with only {"properties": {"a": {"$ref": "https://schemas.test/a.json"}}} calls {} valid, but nothing behind that link was checked. A local reference that points nowhere, such as a misspelled name under $defs, is handled the same way. Staying offline also means a schema you paste cannot make your browser contact anyone.
format. The common formats are checked as real rules: email, date, date-time, time, duration, uri, uri-reference, uri-template, url, hostname, ipv4, ipv6, uuid, regex and the three JSON Pointer formats. url, which is not one of the formats the specifications define, is checked by parsing the value as a URL: it must use the http, https or ftp scheme and name a host, so http://localhost:8080/status passes and mailto:sam@shop.test fails. The specifications allow a validator to treat format as a label only, so a value that fails here can pass in another tool. Formats not on this list are ignored, including names that merely look like JavaScript internals, such as __proto__.
Privacy. Validation is plain JavaScript running in this tab. Nothing you paste is sent to 17x7 or anyone else, saved, or added to the address bar, and once the page has loaded it keeps working without a connection. There is no server-side version: validation is offered only here, in your browser.
Limits
- Drafts 2020-12, 2019-09, 07 and 04 only. Draft-06 runs with draft-07 rules; draft-03 and older are not supported.
$dynamicRefand$dynamicAnchorfrom Draft 2020-12 are not supported and are ignored. A schema that depends on them can come back valid when it should not. The schema{ "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "array", "items": { "$dynamicRef": "#items" }, "$defs": { "foo": { "$dynamicAnchor": "items", "type": "string" } } }calls["foo", 42]valid here, although the reference resolves to the string-only schema and the 42 should fail. A note under the result warns whenever a schema uses either keyword.- The error table shows at most 50 rows, specific errors first. When there are more, a note gives the totals; fix those and validate again to see the rest. Duplicates are merged across the first 1,000 raw errors.
- Duplicate keys inside one JSON object are not reported. The parser keeps the last value, and the validator never sees the earlier ones.
- Numbers are 64-bit floating point. Integers above 9,007,199,254,740,991 lose precision, so
const,enumandmultipleOfchecks on them can be wrong. - Validation runs in this tab and repeats as you type. A very large document can make the page pause on each change, and
uniqueItemscompares every pair of items, so an array of thousands of objects can take several seconds. - Each validation has a work ceiling of 300,000 checks. A schema whose
allOf,anyOf,oneOfor$refbranches multiply, or that refers back to itself without moving into the data, is stopped with a message instead of freezing the page. - Annotation keywords such as
title,description,default,examples,readOnlyanddeprecatednever affect validity and are not checked.
Sources
Frequently asked questions
Which JSON Schema drafts does this validator support?
Draft 2020-12, 2019-09, draft-07 and draft-04. It picks one from the $schema keyword, falls back to 2020-12 when there is none, and lets you override the choice in the Draft menu. Draft-06 schemas run with the draft-07 rules, which mostly added if, then and else. One 2020-12 feature is missing: $dynamicRef and $dynamicAnchor are not supported and are ignored, and a note says so when a schema uses them.
What does the location column mean?
It is a JSON Pointer into your data, as defined in RFC 6901. /items/1/qty is the qty key of the second element of items, because array positions start at zero. An empty pointer, shown as (root), means the whole document. A slash inside a key is written ~1 and a tilde ~0, so a failing key named a/b is shown as /a~1b.
Why does one mistake produce several errors?
Because every rule that encloses the bad value fails too. If a nested value is wrong, the properties or items keyword that leads to it fails, and so does each one above it. The specific error is listed first and the enclosing ones are counted as summary rows and collapsed, so you can usually ignore them.
Why was my regex pattern rejected?
It has a shape that can trigger catastrophic backtracking: a repeated group that itself contains a quantifier or an alternation, like ^(a+)+$ or (x|xx)*, where an optional ? counts as well, so ^(a?a?)+$ is refused too; a backreference such as \1; or more than 256 characters. The check works on shape alone, so it may refuse a pattern that is safe in practice and does not catch every slow one. Flatten the group into a character class or drop the outer repetition: ^(a?a?)+$ matches exactly what ^a*$ does.
Can it follow a $ref to another file or a URL?
No. Only references within the pasted schema resolve: #/$defs/…, #/definitions/…, or a subschema's own $id. Nothing is downloaded. If the data reaches an outside reference you get an error; if it does not, a note names the reference so that a valid result is not taken at face value. Either way, paste the referenced schema into $defs and point the $ref at it.
Does it check format, such as email or date?
Yes, for the common formats, and unknown format names are ignored. For example, checking "sam@shop..test" against "format": "email" gives one error row: "sam@shop..test" is not a valid email address (format "email"). The cause is the empty label between the two dots of the domain. The non-standard url format is checked by parsing the value, which must use http, https or ftp and name a host. The specifications let validators treat format as an annotation only, so another tool may accept a value this one rejects.
Is my JSON uploaded or saved anywhere?
No. Parsing and validation happen in this browser tab, and nothing is sent to a server or stored. The schema and the data are also kept out of the page address, so the copy-link button shares only your draft choice.
Can I validate YAML, or generate a schema to start from?
Convert YAML to JSON first with the YAML to JSON converter; that works for a YAML schema as well as YAML data. To start a schema from a sample document, use the JSON Schema generator, then tighten required, additionalProperties and value ranges by hand.
Disclaimer. Results follow the JSON Schema drafts as this validator implements them; other validators can differ on format checks and edge cases, so test against the one your production system uses.
Last updated . Found a mistake? Tell us — corrections ship the same week.