JSON Schema is a machine-readable contract for JSON data. It describes what a JSON value should look like: which fields are required, which types are allowed, how arrays are shaped, which values are valid, and which extra properties should be rejected.
The important distinction is this: JSON syntax validation answers "is this valid JSON text?" JSON Schema validation answers "does this parsed JSON value match the contract my application expects?"
For example, this is valid JSON:
{
"email": "not-an-email",
"age": -4
}
But it may be invalid for your signup API. A JSON Schema can say that email must be a string with email shape, age must be an integer greater than or equal to 0, and no surprise fields are allowed.
JSON Schema in One Minute
| Question | Practical answer |
|---|---|
| What is JSON Schema? | A JSON document that describes and validates another JSON document |
| Is it the same as JSON? | No. JSON is the data; JSON Schema is the contract for the data |
| Current draft for new schemas? | Draft 2020-12 is the current released draft on json-schema.org |
| Most-used validator in JavaScript? | Ajv |
| Most-used validator in Python? | The jsonschema package |
Does properties make fields required? |
No. Use required for presence |
Does format: "email" always reject bad emails? |
Not unless your validator enforces format assertions |
Does default fill missing values? |
No. In JSON Schema it is an annotation unless your tool adds custom behavior |
What Is JSON Schema?
JSON Schema is itself JSON. A schema document uses keywords such as type, properties, required, items, enum, and additionalProperties to describe valid data.
JSON Schema is maintained as an open specification, and the current specification is split into Core and Validation documents. The Draft 2020-12 release is the current version listed by json-schema.org. The schema below declares that draft explicitly with $schema.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["id", "email"],
"properties": {
"id": { "type": "string" },
"email": { "type": "string", "format": "email" }
},
"additionalProperties": false
}
That schema does not validate anything by itself. A validator library reads the schema, reads the JSON data, and returns pass/fail plus error details.
JSON vs JSON Schema
JSON is an instance:
{
"id": "usr_42",
"email": "ada@example.com",
"role": "admin"
}
JSON Schema is the set of rules for instances like that:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["id", "email", "role"],
"properties": {
"id": { "type": "string", "pattern": "^usr_[0-9]+$" },
"email": { "type": "string", "format": "email" },
"role": { "enum": ["admin", "editor", "viewer"] }
},
"additionalProperties": false
}
A plain JSON parser can tell you whether both snippets are valid JSON syntax. It cannot tell you whether role is required, whether usr_42 follows your ID convention, or whether unexpected properties should be rejected. That is schema validation.
For the data format itself, see What Is JSON?. For syntax-only checks, see How to Validate JSON.
A Minimal Schema and Matching Data
Here is a small JSON document:
{
"id": "ord_1001",
"customerEmail": "ada@example.com",
"totalCents": 1299,
"currency": "USD",
"items": [
{
"sku": "mug-white",
"quantity": 1
}
]
}
Here is a schema for that order:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/schemas/order.schema.json",
"title": "Order",
"type": "object",
"required": ["id", "customerEmail", "totalCents", "currency", "items"],
"properties": {
"id": {
"type": "string",
"pattern": "^ord_[0-9]+$"
},
"customerEmail": {
"type": "string",
"format": "email"
},
"totalCents": {
"type": "integer",
"minimum": 0
},
"currency": {
"enum": ["USD", "EUR", "GBP"]
},
"items": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["sku", "quantity"],
"properties": {
"sku": { "type": "string", "minLength": 1 },
"quantity": { "type": "integer", "minimum": 1 }
},
"additionalProperties": false
}
}
},
"additionalProperties": false
}
This schema says:
- The top-level value must be an object.
- Five fields must be present.
idmust be a string matching the order ID pattern.customerEmailis intended to have email format.totalCentsmust be a non-negative integer.currencymust be one of three allowed values.itemsmust be a non-empty array.- Each item must have
skuandquantity. - Unknown fields are rejected at the order and item levels.
That is the practical value of JSON Schema: it turns "the payload should look right" into a repeatable validation step.
Core JSON Schema Keywords
Most production schemas start with a small set of keywords.
| Keyword | What it does | Common mistake |
|---|---|---|
$schema |
Declares the JSON Schema draft/dialect | Omitting it and letting validators guess |
$id |
Gives the schema a stable URI for references | Changing it casually and breaking $ref users |
type |
Restricts the JSON value type | Using integer when decimals are allowed |
properties |
Defines schemas for named object fields | Assuming it makes fields required |
required |
Lists object keys that must be present | Forgetting it entirely |
items |
Defines array item schemas | Using one schema when tuple positions differ |
prefixItems |
Defines tuple-like array positions in 2020-12 | Porting old draft-07 tuple schemas unchanged |
enum |
Allows one value from a fixed list | Using it for values that change often |
const |
Requires one exact value | Forgetting it is useful for discriminators |
additionalProperties |
Controls unknown object fields | Setting it to false too early for public responses |
$defs |
Holds reusable local schemas | Still using old definitions in new schemas |
$ref |
Reuses another schema | Placing sibling constraints next to $ref without understanding draft behavior |
type
type checks the JSON type.
{
"type": "string"
}
Multiple types are allowed:
{
"type": ["string", "null"]
}
Use that pattern for nullable values in strict JSON Schema. Do not write OpenAPI 3.0-style nullable: true in a standalone JSON Schema.
properties and required
This is the most common beginner mistake:
{
"type": "object",
"properties": {
"email": { "type": "string" }
}
}
That schema says: if email appears, it must be a string. It does not say email must appear.
To require it:
{
"type": "object",
"required": ["email"],
"properties": {
"email": { "type": "string" }
}
}
In code review, I check properties and required together. If a field is business-critical but missing from required, the schema may silently accept incomplete data.
additionalProperties
By default, extra object fields are allowed:
{
"type": "object",
"properties": {
"email": { "type": "string" }
}
}
This accepts:
{
"email": "ada@example.com",
"debug": true
}
To close the object:
{
"type": "object",
"properties": {
"email": { "type": "string" }
},
"additionalProperties": false
}
Use additionalProperties: false deliberately. It is great for internal configs, request bodies, migrations, and safety-critical payloads. For public API responses, it can make old clients reject new fields that were added for forward compatibility.
items and prefixItems
For arrays where every element has the same shape, use items:
{
"type": "array",
"items": {
"type": "string"
}
}
For tuple-like arrays in Draft 2020-12, use prefixItems:
{
"type": "array",
"prefixItems": [
{ "type": "number" },
{ "type": "number" }
],
"items": false,
"minItems": 2,
"maxItems": 2
}
That example describes a coordinate pair such as [12.34, 56.78]. In older Draft-07 schemas, tuple validation was usually expressed with an array-valued items, so this is a common migration detail.
String, Number, and Array Constraints
String constraints:
{
"type": "string",
"minLength": 1,
"maxLength": 80,
"pattern": "^[a-z0-9_-]+$"
}
Number constraints:
{
"type": "integer",
"minimum": 1,
"maximum": 9999,
"multipleOf": 1
}
Array constraints:
{
"type": "array",
"minItems": 1,
"maxItems": 20,
"uniqueItems": true,
"items": {
"type": "string"
}
}
Be careful with uniqueItems on arrays of objects. Validators compare JSON values, not your business identity. Two objects with different non-ID fields are not duplicates just because they share the same id.
enum and const
Use enum for a fixed set:
{
"enum": ["pending", "paid", "failed", "refunded"]
}
Use const for exactly one allowed value:
{
"const": "card"
}
const is especially useful with oneOf when you validate tagged unions:
{
"oneOf": [
{
"type": "object",
"required": ["method", "last4"],
"properties": {
"method": { "const": "card" },
"last4": { "type": "string", "pattern": "^[0-9]{4}$" }
}
},
{
"type": "object",
"required": ["method", "email"],
"properties": {
"method": { "const": "paypal" },
"email": { "type": "string", "format": "email" }
}
}
]
}
The format Keyword Is Easy to Misread
format describes the intended shape of a string:
{
"type": "object",
"properties": {
"id": { "type": "string", "format": "uuid" },
"createdAt": { "type": "string", "format": "date-time" },
"email": { "type": "string", "format": "email" },
"website": { "type": "string", "format": "uri" }
}
}
In Draft 2020-12, the validation specification separates format annotation from format assertion. In plain terms: a validator may collect or expose the format information without rejecting the value unless format assertion is enabled.
That is why format: "email" should not be treated as a security boundary or a deliverability check. It is a useful validation layer, but you still need application checks for things like account existence, DNS, confirmation emails, payment authorization, authorization rules, or uniqueness in a database.
default Does Not Fill Missing Values
This schema is valid:
{
"type": "object",
"properties": {
"pageSize": {
"type": "integer",
"default": 25,
"minimum": 1,
"maximum": 100
}
}
}
But JSON Schema validation does not automatically insert "pageSize": 25 into your data. default is an annotation. Some tools use it to generate forms, docs, or UI placeholders. Some validators offer non-standard options that mutate data. Do not rely on mutation unless your validator is explicitly configured and your team accepts that behavior.
Reuse Schemas with $defs and $ref
Real schemas get repetitive. Use $defs for reusable pieces and $ref to refer to them.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/schemas/customer.schema.json",
"$defs": {
"address": {
"type": "object",
"required": ["line1", "city", "country"],
"properties": {
"line1": { "type": "string", "minLength": 1 },
"line2": { "type": "string" },
"city": { "type": "string", "minLength": 1 },
"country": { "type": "string", "minLength": 2, "maxLength": 2 }
},
"additionalProperties": false
}
},
"type": "object",
"required": ["billingAddress"],
"properties": {
"billingAddress": { "$ref": "#/$defs/address" },
"shippingAddress": { "$ref": "#/$defs/address" }
},
"additionalProperties": false
}
Use stable $id values when schemas are shared across services. If another schema references your $id, changing it is a breaking change.
Combine Schemas with allOf, anyOf, and oneOf
Composition keywords let you build richer constraints:
| Keyword | Meaning | Typical use |
|---|---|---|
allOf |
Must match every schema | Layer common fields with specialized rules |
anyOf |
Must match at least one schema | Allow several valid shapes |
oneOf |
Must match exactly one schema | Tagged unions where shapes must be exclusive |
oneOf is powerful but can be confusing. If the data matches two branches, validation fails because it must match exactly one. Prefer a clear discriminator field with const values:
{
"oneOf": [
{
"type": "object",
"required": ["type", "radius"],
"properties": {
"type": { "const": "circle" },
"radius": { "type": "number", "exclusiveMinimum": 0 }
}
},
{
"type": "object",
"required": ["type", "width", "height"],
"properties": {
"type": { "const": "rectangle" },
"width": { "type": "number", "exclusiveMinimum": 0 },
"height": { "type": "number", "exclusiveMinimum": 0 }
}
}
]
}
Validate JSON Schema in JavaScript with Ajv
Ajv is a common JSON Schema validator for JavaScript and TypeScript projects. Ajv supports multiple JSON Schema drafts, but draft support is tied to the constructor you use.
For Draft 2020-12 schemas:
npm install ajv ajv-formats
const Ajv2020Module = require("ajv/dist/2020");
const addFormatsModule = require("ajv-formats");
const Ajv2020 = Ajv2020Module.default || Ajv2020Module;
const addFormats = addFormatsModule.default || addFormatsModule;
const ajv = new Ajv2020({ allErrors: true });
addFormats(ajv);
const schema = {
$schema: "https://json-schema.org/draft/2020-12/schema",
type: "object",
required: ["id", "email"],
properties: {
id: { type: "string" },
email: { type: "string", format: "email" }
},
additionalProperties: false
};
const validate = ajv.compile(schema);
const data = { id: "usr_42", email: "ada@example.com" };
if (!validate(data)) {
console.error(validate.errors);
}
Two practical Ajv habits:
- Compile the schema once, then reuse the validator function.
- Use
allErrors: truein developer tools, forms, and tests when you want a full error list instead of the first failure.
If you use the main ajv export, you are usually working with draft-07 behavior. For 2019-09 or 2020-12 features such as prefixItems, use the matching Ajv export described in the Ajv schema language docs.
Validate JSON Schema in Python
In Python, the jsonschema package can validate instances against Draft 2020-12 schemas.
pip install jsonschema
from jsonschema import Draft202012Validator, FormatChecker
schema = {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["id", "email"],
"properties": {
"id": {"type": "string"},
"email": {"type": "string", "format": "email"},
},
"additionalProperties": False,
}
data = {"id": "usr_42", "email": "ada@example.com"}
validator = Draft202012Validator(schema, format_checker=FormatChecker())
errors = sorted(validator.iter_errors(data), key=lambda error: error.path)
if errors:
for error in errors:
print(list(error.path), error.message)
else:
print("Valid")
Using iter_errors is friendlier than a single validate() call when you need to show users every problem in a form or file.
Validate Files in CI
For CI, split validation into two steps:
- Check that the files are valid JSON syntax.
- Check that the parsed JSON matches the schema.
Syntax check:
python3 -m json.tool data.json >/dev/null
Schema check with Ajv CLI:
npx ajv-cli validate --spec=draft2020 -s schema.json -d data.json
That split gives clearer failures. Syntax errors are fixed with a formatter or parser. Schema errors are fixed by changing the data or changing the contract.
JSON Schema vs TypeScript, Zod, and OpenAPI
These tools overlap, but they are not interchangeable.
| Tool | Best at | Limitation |
|---|---|---|
| JSON Schema | Language-neutral data contracts and validation | Verbose for complex domain logic |
| TypeScript interfaces | Compile-time checking inside TypeScript code | No runtime validation after compilation |
| Zod/Valibot/Yup | Runtime validation in application code | Not always the best interchange format across languages |
| OpenAPI | HTTP API documentation and request/response contracts | OpenAPI versions use specific JSON Schema dialect behavior |
OpenAPI 3.1 aligns closely with JSON Schema 2020-12. OpenAPI 3.0 is different enough that you should not blindly copy every standalone JSON Schema keyword into an OpenAPI 3.0 document. For example, nullable handling changed between OpenAPI 3.0 and 3.1.
For TypeScript generation from sample JSON or schema, see JSON to TypeScript. Remember: generated interfaces help during development, but external JSON still needs runtime validation.
Generate a Schema from Existing JSON
Tools can infer a starter schema from sample data:
- quicktype can infer types and schemas from JSON samples and URLs.
generate-schemacan infer a JSON Schema from example JSON.- Python's
gensoncan build a schema from one or more samples.
Treat generated schemas as drafts. A generator can see that "status": "paid" is a string, but it cannot know that valid statuses are pending, paid, failed, and refunded. It can see that totalCents is an integer, but it cannot know that negative totals should be rejected unless examples cover that rule.
The review pass is where you add:
requiredfields.enumvalues.minimumandmaximum.minLengthandmaxLength.additionalPropertiespolicy.formatannotations or assertions.oneOfbranches for known variants.
Common JSON Schema Mistakes
| Mistake | Why it hurts | Better approach |
|---|---|---|
Putting a field in properties but not required |
Missing data passes validation | Add the field to required when presence matters |
Relying on format without configuring the validator |
Bad email or URI strings may pass | Enable format validation and still do business checks |
Expecting default to mutate data |
Missing values stay missing | Fill defaults in application code or a documented validator option |
Closing every response with additionalProperties: false |
Old clients may reject new fields | Use strictness for requests/configs; be careful with public responses |
Omitting $schema |
Draft behavior becomes ambiguous | Declare the draft explicitly |
Using oneOf with overlapping branches |
Data can match more than one branch and fail | Add a discriminator-like const field |
| Treating schema validation as security validation | Structure can be valid while content is dangerous | Sanitize, authorize, and check business rules separately |
What JSON Schema Does Not Do
JSON Schema is a strong first gate, but it does not solve every validation problem.
It does not prove:
- An email address can receive mail.
- A user ID exists in your database.
- A user is allowed to perform an action.
- A SKU is currently in stock.
- A string is safe to render as HTML.
- A payment amount matches an invoice.
- Two fields are consistent with external state unless you model that rule locally.
Use JSON Schema at the boundary. Then use application logic for authorization, database lookups, cross-record checks, fraud rules, and side effects.
Can JSON Fix Validate a Schema?
JSON Validator checks strict JSON syntax. That is the first step before schema validation. If the data has trailing commas, comments, single quotes, or unquoted keys, fix syntax first with JSON Fix, then validate the parsed value against your schema with Ajv, jsonschema, or another JSON Schema validator.
Use JSON Viewer when you need to inspect the parsed shape before writing a schema. Use JSON Format Examples when you need clean examples for schema tests.
Frequently Asked Questions
What is JSON Schema used for?
JSON Schema is used to describe and validate JSON data contracts: required fields, allowed types, enum values, string lengths, numeric ranges, array shapes, object properties, and reusable nested structures.
What is the difference between JSON and JSON Schema?
JSON is the data. JSON Schema is a separate JSON document that describes what valid data should look like. A JSON parser checks syntax; a JSON Schema validator checks whether parsed data matches the contract.
Which JSON Schema draft should I use?
Use Draft 2020-12 for new standalone schemas unless your validator, platform, or API tooling requires an older draft. Draft-07 is still common in existing tools. Always declare the draft with $schema.
How do I validate JSON against a schema?
Parse the JSON first, then pass the parsed value and schema to a validator such as Ajv in JavaScript or jsonschema in Python. In CI, run syntax validation separately so parse errors and contract errors are easy to tell apart.
Does JSON Schema validate format values like email and date-time?
It can, but only when the validator is configured to assert formats. In Draft 2020-12, format is an annotation vocabulary by default; enable format assertion or validator-specific format plugins when you need rejection.
Does properties make a JSON field required?
No. properties defines rules for a key if the key appears. To require the key, also list it in the object's required array.
How do I allow null in JSON Schema?
Use a type array such as { "type": ["string", "null"] }. In standalone JSON Schema, do not use OpenAPI 3.0-style nullable: true unless your tooling specifically expects that dialect.
Is JSON Schema the same as TypeScript or OpenAPI?
No. TypeScript types are compile-time checks inside TypeScript. OpenAPI describes HTTP APIs and uses a JSON Schema dialect for schemas. JSON Schema is the language-neutral validation contract for JSON data itself.
Related Guides
- What Is JSON? - understand JSON before writing schemas for it.
- How to Validate JSON - separate syntax validation from schema validation.
- JSON Validator - check strict JSON syntax before schema validation.
- JSON Format Examples - copy realistic examples for tests and docs.
- JSON to TypeScript - generate interfaces from JSON or schema sources.
- How to Format JSON - format JSON before reviewing schema errors.
Sources
- JSON Schema specification - current specification overview, Core, and Validation links.
- JSON Schema Draft 2020-12 - current released draft and release notes.
- Understanding JSON Schema basics - official guide to declaring schemas and
$schema. - Ajv schema language docs - draft support and constructor guidance for JavaScript validation.
- python-jsonschema validation docs - Python validation API and validator classes.
- OpenAPI 3.1 release announcement - OpenAPI 3.1 and JSON Schema 2020-12 compatibility.
Last reviewed July 2026.