"What shape is this JSON supposed to be?" is a question every API consumer eventually asks. JSON Schema is the answer — a way to describe, in JSON itself, exactly what a valid JSON document should look like: which fields are required, what type each one is, and what values are acceptable.
What problem it actually solves
Without a schema, "what does this API return?" is answered by reading documentation (which might be out of date), staring at an example response (which might not show every possible field), or just making a request and seeing what comes back. A schema turns that fuzzy, example-based understanding into something a machine can check automatically — you can validate a document against it and get a precise, actionable answer: valid, or not, with a specific reason why.
A first example
Say you have this JSON, describing a product:
{
"id": 101,
"name": "Wireless Mouse",
"price": 29.99,
"inStock": true
}
Here's a JSON Schema that describes it:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"price": { "type": "number" },
"inStock": { "type": "boolean" }
},
"required": ["id", "name", "price", "inStock"]
}
Read it almost like plain English: this is an object, it has four properties with these specific types, and all four are required. A validator library takes this schema plus a JSON document and tells you whether the document satisfies it.
The building blocks
type— the JSON type expected:object,array,string,number,integer,boolean, ornull.properties— for an object, this lists each expected key and describes what its value should look like (which can itself be another full schema, allowing nesting).required— an array of property names that must be present. Anything not listed here is treated as optional.items— for an array, describes the schema every element in the array must satisfy.enum— restricts a value to a specific, fixed list of options, like"status": { "enum": ["active", "inactive", "pending"] }.
Adding constraints beyond just type
Type alone often isn't specific enough. JSON Schema lets you add real constraints on top:
{
"type": "object",
"properties": {
"age": { "type": "integer", "minimum": 0, "maximum": 130 },
"email": { "type": "string", "format": "email" },
"username": { "type": "string", "minLength": 3, "maxLength": 20 }
}
}
Now age isn't just "any integer" — it has to be a sane human age. username
has to be a reasonable length. This is the difference between a schema that just checks "is this the right
shape" and one that actually enforces meaningful business rules.
Nested objects and arrays
A schema can describe arbitrarily nested structures. Here's one for an order with a list of line items:
{
"type": "object",
"properties": {
"orderId": { "type": "string" },
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"sku": { "type": "string" },
"quantity": { "type": "integer", "minimum": 1 }
},
"required": ["sku", "quantity"]
}
}
},
"required": ["orderId", "items"]
}
Notice how items (the array-of-objects field) has its own nested type,
properties, and required — schemas compose naturally, which is what lets
them describe realistically complex, deeply nested API payloads.
What people actually use it for
- Validating API requests. Reject a malformed request before it ever touches your business logic, with a clear, specific error message about exactly what's wrong.
- Documenting a data contract. A schema is a precise, unambiguous, machine-readable description of what a payload looks like — more reliable than a prose description that can drift out of date.
- Powering editor autocomplete. Tools like VS Code use JSON Schema to autocomplete and validate config files (like
package.json) as you type, catching typos before you even save. - Generating other artifacts. Some tools can generate TypeScript types, form UIs, or API documentation directly from a JSON Schema, so you only maintain the shape of your data in one place.
A realistic way to get started
Writing a schema by hand from scratch is tedious and error-prone for anything beyond a small object. A much
more practical starting point is to take a real, representative example of your data and infer a schema from
it automatically, then refine the result — loosen fields that were only sometimes present in your
sample, add constraints (like minimum or format) that the raw data alone can't
tell you, and remove any required entries you know are actually optional across other real
records.
Frequently Asked Questions
minimum, maxLength, or enum) that can't be inferred from a single example.pattern keyword with a regular expression, or the built-in format keyword for common patterns like email or date-time (though format validation support varies somewhat by validator library, so check what your specific library enforces).