Introduction: Why JSON Schema Matters
JSON has become the universal data format for APIs, configuration files, and data exchange. But raw JSON has no built-in way to enforce structure. A JSON document can contain any combination of types, keys, and values — and there is nothing inherent to the format that tells you whether a particular document is valid for your use case. JSON Schema solves this problem by providing a vocabulary for describing and validating the structure of JSON data.
JSON Schema is itself written in JSON. It defines the expected shape of your data: which fields are required, what types they should be, acceptable value ranges, string patterns, and how sub-schemas compose together. Validators take a schema and a JSON instance, then report whether the instance conforms — and if not, exactly which constraints were violated. Use our free JSON Schema Validator to test your schemas and data interactively as you follow this guide.
This guide covers JSON Schema Draft 7 (the most widely supported draft), walks through every major validation keyword, explains composition and reuse with $ref, and shows real-world applications from API contracts to form generation.
What Is JSON Schema?
JSON Schema is a declarative language for annotating and validating JSON documents. It was first proposed by Kris Zyp in 2009 and has evolved through multiple drafts. Draft 7, published in 2018, remains the most broadly supported version across validation libraries in JavaScript, Python, Java, Go, and other languages. Later drafts (2019-09 and 2020-12) added features like $dynamicRef and vocabulary support, but Draft 7 covers the vast majority of real-world validation needs.
A JSON Schema document is a JSON object that uses reserved keywords to describe constraints. At minimum, a schema declares the expected type of the root value. More complex schemas describe nested objects, arrays with specific item types, conditional rules, and references to shared definitions. Here is a minimal example:
{"type": "object", "properties": {"name": {"type": "string"}, "age": {"type": "integer", "minimum": 0}}, "required": ["name"]}
This schema states that the root value must be an object, it may have a name (string) and an age (non-negative integer), and name is required. Any JSON object with a string name field passes. An object missing name, or one where age is negative or a string, fails validation.
Core Keywords: type, required, and properties
The type Keyword
The type keyword restricts which JSON type a value may be. JSON Schema recognizes seven types: string, number, integer, boolean, object, array, and null. Note that integer is a JSON Schema type that constrains numbers to whole values — JSON itself has no integer type. You can also pass an array of types to allow multiple: {"type": ["string", "null"]} permits either a string or null.
The properties Keyword
For objects, properties maps each expected key to a sub-schema that validates the corresponding value. Properties not listed in the schema are allowed by default — use additionalProperties to control this behavior. Setting "additionalProperties": false rejects any keys not explicitly declared, which is useful for strict API contracts where unexpected fields indicate a client error.
The required Keyword
The required keyword is an array of property names that must be present in the object. A missing required property causes a validation error. Note that required only checks for the presence of the key — it does not validate the value. A required field set to null still passes the required check unless you also constrain the type.
String Validation Keywords
JSON Schema provides several keywords for validating string values beyond just checking the type:
- minLength / maxLength — Constrain the number of characters.
{"type": "string", "minLength": 1, "maxLength": 255}ensures a non-empty string no longer than 255 characters. - pattern — A regular expression (ECMA 262 dialect) the string must match. For example,
{"pattern": "^[A-Z]{2}-\\d{4}$"}matches strings likeUS-1234. The regex is implicitly anchored to the full string in most validators, but explicitly anchoring with^and$is best practice. - format — A semantic hint for common string formats:
email,uri,date,date-time,ipv4,ipv6,hostname,uuid, and others. In Draft 7, format is an annotation by default — validators may or may not enforce it depending on configuration. Always pairformatwith apatternif strict validation is required.
Numeric Validation Keywords
For number and integer types, JSON Schema offers range and divisibility constraints:
- minimum / maximum — Inclusive bounds.
{"type": "integer", "minimum": 1, "maximum": 100}accepts integers from 1 to 100. - exclusiveMinimum / exclusiveMaximum — Exclusive bounds. In Draft 7 these are numeric values, not booleans (Draft 4 used booleans).
{"exclusiveMinimum": 0}rejects zero but accepts any positive number. - multipleOf — The value must be evenly divisible by this number.
{"type": "number", "multipleOf": 0.01}ensures exactly two decimal places, useful for currency amounts.
Array Validation Keywords
Arrays in JSON Schema can be validated for both structure and content:
- items — Defines the schema for each element.
{"type": "array", "items": {"type": "string"}}requires every element to be a string. In Draft 7,itemscan also be an array of schemas for tuple validation, where each position has its own schema. - minItems / maxItems — Constrain array length.
{"minItems": 1}rejects empty arrays. - uniqueItems — When set to
true, all elements must be distinct. Useful for tag lists or ID arrays where duplicates are meaningless. - contains — At least one element must match the given schema.
{"contains": {"type": "number", "minimum": 10}}requires that at least one element is a number greater than or equal to 10.
The enum and const Keywords
The enum keyword restricts a value to a fixed set of allowed values. The values can be of any type — strings, numbers, objects, arrays, booleans, or null. This is the primary way to implement drop-down or select-style constraints:
{"type": "string", "enum": ["draft", "published", "archived"]}
The const keyword (introduced in Draft 6) restricts a value to exactly one allowed value. It is equivalent to an enum with a single element and is commonly used in conditional schemas to match a discriminator field:
{"properties": {"type": {"const": "circle"}, "radius": {"type": "number"}}}
Composition Keywords: allOf, anyOf, oneOf, and not
JSON Schema's composition keywords let you combine multiple schemas using logical operators. These are essential for modeling complex data structures and conditional validation.
allOf — Logical AND
allOf requires a value to match every sub-schema in the array. This is commonly used to combine a base schema with additional constraints or to merge multiple schema fragments:
{"allOf": [{"type": "object", "properties": {"id": {"type": "string"}}}, {"required": ["id", "name"]}]}
The value must satisfy both sub-schemas simultaneously. If any sub-schema fails, the overall validation fails.
anyOf — Logical OR
anyOf requires a value to match at least one sub-schema. Validators typically report errors from all branches when none match, which can produce verbose output. Use anyOf when multiple representations are acceptable:
{"anyOf": [{"type": "string", "format": "date"}, {"type": "string", "format": "date-time"}]}
oneOf — Exclusive OR
oneOf requires a value to match exactly one sub-schema — not zero, not more than one. This is ideal for discriminated unions where a value must be one specific variant. If the value matches two or more sub-schemas, validation fails even though each individual match succeeds:
{"oneOf": [{"type": "object", "properties": {"type": {"const": "circle"}, "radius": {"type": "number"}}, "required": ["type", "radius"]}, {"type": "object", "properties": {"type": {"const": "rectangle"}, "width": {"type": "number"}, "height": {"type": "number"}}, "required": ["type", "width", "height"]}]}
not — Logical Negation
not inverts a schema: the value must not match the given sub-schema. {"not": {"type": "null"}} rejects null values. Use not sparingly — it produces confusing error messages because the validator must explain why a non-match was expected.
Reuse with $ref and definitions
Real-world schemas often reuse the same sub-schema in multiple places — for example, an address object that appears in both a shipping and billing field. Rather than duplicating the schema, JSON Schema provides $ref for referencing shared definitions.
In Draft 7, reusable schemas are placed under a definitions key (renamed to $defs in later drafts) and referenced with a JSON Pointer:
{"definitions": {"address": {"type": "object", "properties": {"street": {"type": "string"}, "city": {"type": "string"}, "zip": {"type": "string", "pattern": "^\\d{5}$"}}, "required": ["street", "city", "zip"]}}, "type": "object", "properties": {"shipping": {"$ref": "#/definitions/address"}, "billing": {"$ref": "#/definitions/address"}}}
When the validator encounters $ref, it replaces the reference with the target schema and validates normally. References can point to external files or URLs, enabling schema libraries shared across teams. However, circular references require careful handling — most validators support them but some have depth limits.
Conditional Schemas: if / then / else
Draft 7 introduced if, then, and else keywords for conditional validation. If the value matches the if schema, the then schema is also applied. If it does not match, the else schema is applied instead. This is cleaner than complex oneOf constructs for many use cases:
{"type": "object", "properties": {"paymentMethod": {"type": "string", "enum": ["credit_card", "bank_transfer"]}}, "if": {"properties": {"paymentMethod": {"const": "credit_card"}}}, "then": {"properties": {"cardNumber": {"type": "string", "pattern": "^\\d{16}$"}}, "required": ["cardNumber"]}, "else": {"properties": {"iban": {"type": "string"}}, "required": ["iban"]}}
This schema requires cardNumber when paymentMethod is credit_card, and iban when it is bank_transfer. Conditional schemas are powerful but can become hard to debug in deeply nested structures.
Real-World Use Cases
API Contract Validation
JSON Schema is the backbone of API contract testing. OpenAPI (Swagger) specifications use JSON Schema to define request and response bodies. By validating incoming requests against a schema, APIs reject malformed data before it reaches business logic. This eliminates entire classes of bugs — missing fields, wrong types, out-of-range values — at the boundary layer. Many API gateways (Kong, AWS API Gateway, Apigee) support schema validation natively, returning 400 errors with detailed violation messages.
For response validation, schemas serve as contract tests: you validate API responses in your test suite to catch breaking changes before deployment. If a backend team accidentally removes a required field or changes a type, schema validation catches it immediately.
Configuration File Validation
Modern tools ship JSON Schemas for their config files. VS Code, ESLint, TypeScript (tsconfig.json), and GitHub Actions all publish schemas that editors like VS Code use for autocomplete and inline validation. When you get red squiggles under a misspelled key in package.json, that is JSON Schema validation at work. You can publish schemas for your own configuration files to give users the same experience.
Form Generation
Libraries like React JSON Schema Form, Formly, and JSONForms generate complete HTML forms from JSON Schemas. The schema defines the fields, types, constraints, and labels; the library renders input elements, validates on submit, and produces a JSON object matching the schema. This pattern dramatically reduces form boilerplate and ensures the frontend and backend agree on the data model. Adding a field means updating the schema — the form and validation update automatically.
Data Pipeline Validation
ETL pipelines and event-driven architectures use JSON Schema to validate data at ingestion. Apache Kafka with the Confluent Schema Registry validates messages against schemas before they enter topics. AWS EventBridge uses JSON Schema-compatible patterns for event filtering. Snowplow Analytics validates tracking events against schemas to prevent corrupted data from reaching the data warehouse. Without schema validation, bad data propagates silently and corrupts downstream analytics.
Best Practices for JSON Schema Design
Writing effective schemas requires balancing strictness with flexibility. Follow these guidelines to create schemas that are maintainable, useful, and developer-friendly:
- Start strict, loosen as needed. Begin with
"additionalProperties": falseand explicitrequiredarrays. It is far easier to relax constraints than to tighten them after clients depend on loose validation. - Use descriptive titles and descriptions. JSON Schema supports
title,description, andexamplesannotations on every sub-schema. These appear in documentation generators and editor tooltips, making the schema self-documenting. - Prefer $ref for reuse. Never duplicate a sub-schema. Extract shared structures into
definitionsand reference them. This ensures consistency and makes updates a single-point change. - Use enum for closed sets. If a field accepts a known list of values (status codes, country codes, payment methods), use
enumrather than a permissivestringtype. This catches typos and invalid values immediately. - Combine type with format and pattern. The
formatkeyword alone may not be enforced. Always pair it with apatternregex for critical fields like emails, dates, or identifiers. - Version your schemas. Include a
$idwith a versioned URL. When you make breaking changes, increment the version. This prevents mismatches between producers and consumers. - Test schemas against valid and invalid examples. Maintain a suite of example documents — both valid and intentionally invalid — and run them through your validator in CI. This catches regressions when schemas evolve.
- Keep schemas shallow. Deeply nested
allOf/oneOfchains become impossible to debug. If your schema exceeds three levels of composition, consider restructuring with$refand named definitions.
Common Pitfalls and How to Avoid Them
Even experienced developers make mistakes with JSON Schema. Here are the most frequent issues and their solutions:
- Confusing required with nullable. A property listed in
requiredmust be present but can still benullunless the type excludes it. If a field must exist and be non-null, declare both"required": ["field"]and{"type": "string"}(without includingnullin the type). - Forgetting additionalProperties interacts with allOf. When using
allOfto extend a base schema,additionalPropertiesis evaluated against the current schema'spropertiesonly — it does not see properties defined in otherallOfbranches. This causes unexpected validation failures. The workaround is to list all expected properties in the schema that declaresadditionalProperties. - Overly complex oneOf with overlapping schemas. If two
oneOfbranches can both match the same value, validation fails becauseoneOfrequires exactly one match. Use a discriminator property (like"type": {"const": "circle"}) to ensure branches are mutually exclusive. - Ignoring default values. The
defaultkeyword is an annotation — validators do not inject default values into the data. Your application code must handle defaults explicitly. - Not testing edge cases. Empty strings, zero-length arrays, negative numbers, and null values are common edge cases that schemas should explicitly handle. A schema that only tests the happy path will let invalid data through in production.
JSON Schema Draft 7 Keyword Reference
For quick reference, here is a summary of the most important Draft 7 validation keywords organized by category:
- General:
type,enum,const,default,$ref,definitions - Strings:
minLength,maxLength,pattern,format - Numbers:
minimum,maximum,exclusiveMinimum,exclusiveMaximum,multipleOf - Objects:
properties,required,additionalProperties,patternProperties,minProperties,maxProperties,propertyNames,dependencies - Arrays:
items,additionalItems,minItems,maxItems,uniqueItems,contains - Composition:
allOf,anyOf,oneOf,not,if/then/else - Annotations:
title,description,examples,readOnly,writeOnly
Conclusion: Schema Validation Is Non-Negotiable
JSON Schema validation transforms JSON from a loose, anything-goes format into a rigorous data contract. Whether you are building APIs, validating configuration files, generating forms, or guarding data pipelines, JSON Schema provides the vocabulary to describe exactly what valid data looks like — and to reject everything else with clear, actionable error messages.
Draft 7 covers the vast majority of validation needs with a stable, widely-supported feature set. Start with the core keywords — type, properties, required, enum — and layer in composition (allOf, oneOf), conditionals (if/then/else), and reuse ($ref) as your data models grow. Validate early, validate often, and let your schemas serve as living documentation of your data contracts. Try our JSON Schema Validator to experiment with schemas and see validation results in real time.