Valid JSON means the syntax is correct — balanced braces, quoted keys, proper types. But syntax isn't meaning. An API might return an object with a string where a number should be, or omit a required field entirely, and a syntax validator would pass it happily. JSON Schema closes that gap: it's a JSON document that describes what valid data looks like, and a validator can check any instance against it.
A schema in practice
Here's a minimal schema for a user object:
- The schema declares "type": "object" and "required": ["id", "email"].
- Each property is typed: id must be an integer, email a string with a format hint, age an integer between 13 and 120.
- Additional properties are allowed unless "additionalProperties": false is set.
An instance like {"id": "abc", "email": "ada@example.com"} fails immediately: id has the wrong type. That single check catches a whole class of integration bugs before they reach the UI.
Where JSON Schema earns its keep
- API contracts — validate request bodies at the edge so bad data fails fast with a useful message.
- Documentation — a schema is human-readable documentation that never goes stale because it's also the test.
- Form generation — schemas can drive dynamic forms (fields, defaults, validation rules) for admin panels and configurators.
- Data interchange — when you ingest third-party data, a schema check turns silent garbage into a clear error.
When it's overkill
For small internal payloads you control on both ends, a full schema adds ceremony without much payoff. And JSON Schema validates structure, not business rules — a schema can say age is 13–120, but it can't know that a discount code expired. Use it as one layer, not the only layer.
Draft versions matter
JSON Schema drafts (2020-12, 2019-09, draft-07…) differ in keywords and behavior. Make sure your validator and your schema declare the same draft, or the same schema can pass in one tool and fail in another.
Validate a schema or an instance
Paste a JSON document into ForgePlug's JSON Validator to confirm it's syntactically sound before you write the schema around it.
Open JSON ValidatorThe default that surprises everyone
By default, JSON Schema allows properties you did not define. A schema describing id and email will happily validate an object that also contains role, isAdmin, and anything else the sender felt like including — because the specification treats a schema as a set of constraints rather than an exhaustive definition.
Setting additionalProperties to false changes that, and it is worth doing deliberately rather than by habit. Rejecting unknown fields is right for input you are about to trust and act on, where an unexpected property may indicate a mistake or an injection attempt. It is wrong for consuming an API you do not control, where the provider adding a new response field would suddenly break your integration for no good reason.
The related trap is required. Listing a property in the schema does not make it mandatory — a property is only required if it appears in the required array. Schemas that carefully describe ten fields and omit required entirely will validate an empty object without complaint.
Errors that are technically correct and practically useless
Validation output is the part most teams underestimate. Composition keywords like anyOf and oneOf produce error messages that report every branch that failed, so a schema with five alternatives generates five sets of errors for one bad field. Passed straight to a user, that is incomprehensible; passed to a support inbox, it generates tickets.
Two things help. Most validators expose an error path pointing at the exact location in the document, which is what you want to surface rather than the raw message. And many implementations support custom error text per keyword, so "must match pattern ^[A-Z]{2}[0-9]{4}$" can become "Reference should be two letters followed by four digits". The schema is doing its job either way; the difference is whether a human can act on the result.
Keeping schemas maintainable
As schemas grow, the same shapes repeat — an address, a money amount, a timestamp. Defining those once under $defs and referencing them with $ref keeps a large schema readable and means a change to the address format happens in one place. It also makes the schema legible as documentation, which is much of its value.
One practical warning about formats: keywords such as format: "email" or format: "date-time" are treated as annotations rather than assertions by default in recent drafts, and several validators do not enforce them unless explicitly configured to. A schema that appears to validate email addresses may be doing nothing at all. Check your validator's behaviour rather than assuming, and use pattern where the constraint genuinely matters.
