JSON Schema is a vocabulary — written in JSON — for describing the shape of JSON data. It answers questions a type system cannot: not just “is this a string?” but “is it a string of at least eight characters matching this pattern, and is it required when type is email?”
It is used for API request validation, configuration validation, form generation, documentation, and test data generation. One artefact, several jobs.
A schema, annotated
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/schemas/user.json",
"title": "User",
"type": "object",
"required": ["id", "email", "status"],
"additionalProperties": false,
"properties": {
"id": { "type": "integer", "minimum": 1 },
"email": { "type": "string", "format": "email", "maxLength": 254 },
"status": { "enum": ["active", "invited", "disabled"] },
"age": { "type": "integer", "minimum": 18, "maximum": 130 },
"tags": {
"type": "array",
"items": { "type": "string" },
"uniqueItems": true,
"maxItems": 20
},
"profile": {
"type": ["object", "null"],
"properties": { "bio": { "type": "string", "maxLength": 500 } }
}
}
}$schemadeclares which draft the document uses. Validators need it.$idgives the schema a stable identity so other schemas can reference it with$ref.requiredis a list of property names — it is *not* a flag on each property, which is the single most common mistake.additionalProperties: falserejects anything not listed. Powerful, and a compatibility hazard: it means adding a field to your API is a breaking change for anyone validating with this schema.enumis where a schema earns its keep. No inference tool can guess thatstatushas exactly three legal values.
The keywords worth knowing
Composition
allOf— must satisfy every subschema. Used for “base object plus these extra fields”.anyOf— must satisfy at least one.oneOf— must satisfy exactly one. Use it for discriminated unions; error messages are worse thananyOfwhen it fails.not— must not satisfy the subschema.if/then/else— conditional requirements, such as “iftypeiscard,cardNumberis required”.
Strings
minLength, maxLength, pattern (a regular expression), and format for date-time, date, email, uuid, uri, ipv4 and others. Note that format is an annotation by default — many validators do not enforce it unless you turn enforcement on. Ajv needs the ajv-formats package.
Numbers
minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf. Note that type: "integer" accepts 1.0, because that is mathematically an integer.
Generating a first draft
Writing a schema from scratch for an existing API is tedious. The schema generator infers one from a sample: types for every property, required fields derived from what is present across array items, merged item schemas, and detected string formats.
What inference cannot give you, and what you must add by hand:
enumvalues — a sample shows the values that occurred, not the values that are legal.- Ranges and lengths — that
agemust be at least 18 is a business rule, not a property of the data. descriptionfor each field — the part future readers will actually use.- Conditional requirements between fields.
- Whether
additionalPropertiesshould be closed.
Evolving a schema without breaking clients
The compatibility rules are the same as for any interface:
- Safe: adding an optional property; widening a range; adding an enum value that consumers treat as unknown; relaxing a pattern.
- Breaking: adding a required property; removing a property; narrowing a type; removing an enum value; setting
additionalProperties: falseon an existing schema.
Keep old versions of the schema in version control and run a structural diff between them before you publish. Type changes and new required fields are exactly what that diff surfaces.
Schema and types together
A schema validates at runtime; generated types check at compile time. They are complementary, and they should be generated from the same source of truth so they cannot drift. Generate both from the same sample document — the type generator and the schema generator use the same inference engine — and regenerate both when the API changes.