Home → JSON Schema Guide

JSON Schema: Complete Guide to Validating JSON Data (2026)

📅 Updated April 2026 ⏱ 14 min read 🛠 Developer guide

JSON Schema is a powerful standard that lets you describe and validate the structure of JSON data. Whether you're building a REST API, validating configuration files, or enforcing data contracts between microservices, JSON Schema gives you a declarative, language-agnostic way to specify exactly what your JSON must look like — and reject anything that doesn't conform.

This guide covers everything from the basics of schema structure to advanced topics like $ref references, conditional validation with if/then/else, and practical validation in both JavaScript (using Ajv) and Python (using jsonschema). By the end, you'll be able to write production-quality schemas for real-world APIs.

Validate JSON against a schema right now

Use the free JSON Schema Validator — paste your schema and data to see errors instantly.

Open Schema Validator →

1. What is JSON Schema?

JSON Schema is a vocabulary that allows you to annotate and validate JSON documents. It is itself written in JSON, which means you can store schemas in files, send them over APIs, and process them with standard JSON tools. The official home of the specification is json-schema.org, where the IETF Internet-Draft documents live.

The specification has evolved through several draft versions. The most commonly used today are:

When writing schemas for new projects, specify the draft with the $schema keyword so validators know which rules to apply. Most validator libraries still default to draft-07 behavior, which remains the sweet spot of stability and feature richness for 2026 projects.

Why JSON Schema matters: Without schema validation, your API accepts any shape of data and you discover problems at runtime — often in production. With JSON Schema, you catch type mismatches, missing fields, and malformed values at the boundary of your system, before they corrupt your database or crash your service.

2. Basic Schema Structure

Every JSON Schema is a JSON object. The four most fundamental keywords are $schema, type, properties, and required. Here is a minimal schema that validates a user object:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "User",
  "description": "A registered user in the system",
  "type": "object",
  "properties": {
    "id": {
      "type": "integer",
      "description": "Unique user identifier"
    },
    "username": {
      "type": "string",
      "minLength": 3,
      "maxLength": 30
    },
    "email": {
      "type": "string",
      "format": "email"
    },
    "active": {
      "type": "boolean"
    }
  },
  "required": ["id", "username", "email"]
}

This schema validates the following JSON successfully:

{
  "id": 42,
  "username": "alice",
  "email": "alice@example.com",
  "active": true
}

The $schema keyword is a URI that identifies the schema dialect. The title and description keywords are purely for documentation — validators ignore them. The real work is done by type, properties, and required.

3. Primitive Type Validation

JSON Schema supports six primitive types that map directly to JSON's native data types:

Schema typeJSON exampleNotes
"string""hello"Any UTF-8 string
"number"3.14, 42Integers and floats
"integer"42Only whole numbers
"boolean"true, falseStrict booleans only
"null"nullJSON null literal
"array"[1, 2, 3]Ordered list
"object"{"key": "val"}Key-value pairs

You can also accept multiple types by using an array: "type": ["string", "null"]. This is the correct way to express a nullable field — it means the value can be a string or null, but not a number or boolean.

If you omit the type keyword entirely, the schema accepts any JSON value. An empty object {} is a valid schema that passes everything — useful as a placeholder but be careful not to leave it accidentally in production schemas.

4. String Validation

Strings have several keywords that let you constrain their length, content, and format:

Length constraints

{
  "type": "string",
  "minLength": 1,
  "maxLength": 255
}

minLength and maxLength count Unicode code points, not bytes. The string "café" has 4 code points, even though it may be 5 bytes in UTF-8.

Pattern (regular expressions)

{
  "type": "string",
  "pattern": "^[a-z][a-z0-9_]{2,29}$"
}

The pattern keyword takes an ECMA-262 regular expression. The regex is tested as a search (not an anchored match), so include ^ and $ anchors if you want to match the entire string. The example above validates lowercase usernames of 3–30 characters starting with a letter.

Format

The format keyword provides semantic validation for common string types:

Important: Format validation is optional in JSON Schema — validators are not required to enforce it unless explicitly configured. In Ajv, install the ajv-formats package and call addFormats(ajv). In Python's jsonschema, pass format_checker=jsonschema.FormatChecker() to the validate call.

5. Number Validation

Both "number" and "integer" types support range and divisibility constraints:

{
  "type": "number",
  "minimum": 0,
  "maximum": 100,
  "exclusiveMaximum": 100,
  "multipleOf": 0.5
}

The keywords work as follows:

Draft-04 note: In draft-04, exclusiveMinimum and exclusiveMaximum were booleans that modified the corresponding minimum/maximum keywords. From draft-06 onward, they became standalone numeric keywords. This is a common source of confusion when mixing libraries and schema versions.

6. Object Validation

Objects are the most common JSON structure in real APIs. JSON Schema gives you precise control over which properties are allowed, required, or prohibited:

properties and required

{
  "type": "object",
  "properties": {
    "firstName": { "type": "string" },
    "lastName":  { "type": "string" },
    "age":       { "type": "integer", "minimum": 0 }
  },
  "required": ["firstName", "lastName"]
}

The properties keyword defines schemas for known properties. It does not make those properties required — that is the job of the required keyword, which takes an array of property names that must be present.

additionalProperties

{
  "type": "object",
  "properties": {
    "x": { "type": "number" },
    "y": { "type": "number" }
  },
  "additionalProperties": false
}

Setting additionalProperties to false rejects any property not listed in properties. This is the "closed" schema pattern. You can also set it to a schema — "additionalProperties": {"type": "string"} — to allow extra properties as long as they match the given type.

patternProperties

{
  "type": "object",
  "patternProperties": {
    "^S_": { "type": "string" },
    "^N_": { "type": "number" }
  }
}

patternProperties applies schemas to all properties whose names match a regex. The example above requires all S_-prefixed properties to be strings and all N_-prefixed properties to be numbers.

minProperties and maxProperties

Use minProperties and maxProperties to constrain how many properties an object may contain. This is useful for generic key-value maps where you want to limit the number of entries.

7. Array Validation

Arrays are validated with items, which applies a schema to every element in the array, plus several keywords for length and uniqueness:

{
  "type": "array",
  "items": {
    "type": "string",
    "minLength": 1
  },
  "minItems": 1,
  "maxItems": 10,
  "uniqueItems": true
}

This schema validates an array of 1 to 10 non-empty strings, with no duplicates — a tag list, for example.

Tuple validation (prefixItems)

In JSON Schema 2020-12, prefixItems replaces the array form of items for tuple validation:

{
  "type": "array",
  "prefixItems": [
    { "type": "number" },
    { "type": "string" },
    { "type": "boolean" }
  ],
  "items": false
}

This validates a fixed-length tuple [42, "hello", true] where the first element is a number, the second is a string, and the third is a boolean. Setting items: false disallows any additional elements beyond the three defined positions.

contains

The contains keyword validates that at least one element in the array matches a given schema. Add minContains and maxContains to control how many matching elements are required:

{
  "type": "array",
  "contains": { "type": "number", "minimum": 100 },
  "minContains": 1,
  "maxContains": 3
}

8. Combining Schemas

JSON Schema provides four composition keywords that let you build complex validation logic from simpler schemas:

allOf — all schemas must pass

{
  "allOf": [
    { "type": "string" },
    { "minLength": 5 },
    { "pattern": "^[A-Z]" }
  ]
}

The data must be valid against every schema in the array. This is the equivalent of a logical AND. Use it to layer additional constraints onto a base schema.

anyOf — at least one schema must pass

{
  "anyOf": [
    { "type": "string" },
    { "type": "number" }
  ]
}

The data must be valid against at least one schema. This is a logical OR. It is the correct way to express union types in JSON Schema.

oneOf — exactly one schema must pass

{
  "oneOf": [
    { "properties": { "type": { "const": "circle" } }, "required": ["type", "radius"] },
    { "properties": { "type": { "const": "rect" } }, "required": ["type", "width", "height"] }
  ]
}

oneOf validates that exactly one sub-schema matches. It is more expensive to evaluate than anyOf because it must check all schemas even after one passes. Use it for true discriminated unions where the sub-schemas are mutually exclusive.

not — schema must not pass

{
  "not": { "type": "null" }
}

The data must fail the given schema. This example rejects null values. not is useful for excluding specific values while allowing everything else.

9. $ref and $defs for Reusable Schemas

As schemas grow, you'll find yourself repeating the same sub-schemas in multiple places. The $defs keyword (called definitions in older drafts) is the standard place to put reusable schema fragments, and $ref is used to reference them:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$defs": {
    "address": {
      "type": "object",
      "properties": {
        "street":  { "type": "string" },
        "city":    { "type": "string" },
        "country": { "type": "string", "minLength": 2, "maxLength": 2 }
      },
      "required": ["street", "city", "country"]
    },
    "positiveInteger": {
      "type": "integer",
      "minimum": 1
    }
  },
  "type": "object",
  "properties": {
    "id":             { "$ref": "#/$defs/positiveInteger" },
    "billingAddress": { "$ref": "#/$defs/address" },
    "shippingAddress":{ "$ref": "#/$defs/address" }
  },
  "required": ["id", "billingAddress"]
}

The $ref value is a JSON Pointer. The # refers to the root of the current schema document. /$defs/address is the path to the address definition. This DRY (Don't Repeat Yourself) approach means you change the address schema in one place and all references automatically pick up the change.

You can also reference external schemas by providing a full URI: "$ref": "https://example.com/schemas/address.json". Most validators support loading external $refs, either automatically or through a registered schema store.

10. Conditional Validation

One of the most useful features added in draft-07 is the if/then/else construct. It lets you apply different validation rules depending on whether the data matches a condition:

{
  "type": "object",
  "properties": {
    "paymentMethod": { "type": "string", "enum": ["card", "bank", "paypal"] },
    "cardNumber":    { "type": "string", "pattern": "^[0-9]{16}$" },
    "bankAccount":   { "type": "string" },
    "paypalEmail":   { "type": "string", "format": "email" }
  },
  "required": ["paymentMethod"],
  "if": {
    "properties": { "paymentMethod": { "const": "card" } }
  },
  "then": {
    "required": ["cardNumber"]
  },
  "else": {
    "if": {
      "properties": { "paymentMethod": { "const": "bank" } }
    },
    "then": {
      "required": ["bankAccount"]
    },
    "else": {
      "required": ["paypalEmail"]
    }
  }
}

If the if schema validates successfully, the then schema is applied. If it fails, the else schema is applied. The else branch can itself contain another if/then/else for chained conditions.

This pattern is ideal for forms and API requests where different fields become required depending on a discriminator field like paymentMethod, subscriptionType, or role.

11. Validating JSON Schema in JavaScript with Ajv

Ajv (Another JSON Validator) is the fastest and most feature-complete JSON Schema validator for JavaScript and Node.js. It pre-compiles schemas to JavaScript functions for maximum performance.

Installation

npm install ajv ajv-formats

Basic usage

import Ajv from "ajv";
import addFormats from "ajv-formats";

const ajv = new Ajv({ allErrors: true });
addFormats(ajv);

const schema = {
  type: "object",
  properties: {
    name:  { type: "string", minLength: 1 },
    email: { type: "string", format: "email" },
    age:   { type: "integer", minimum: 0, maximum: 150 }
  },
  required: ["name", "email"],
  additionalProperties: false
};

const validate = ajv.compile(schema);

const data = { name: "Alice", email: "alice@example.com", age: 28 };

if (validate(data)) {
  console.log("Valid!");
} else {
  console.error("Validation errors:", validate.errors);
}

Reading Ajv error messages

When validation fails, validate.errors is an array of error objects. Each object has these key fields:

// Example error output for missing required field:
[
  {
    instancePath: "",
    schemaPath: "#/required",
    keyword: "required",
    params: { missingProperty: "email" },
    message: "must have required property 'email'"
  }
]

Ajv with JSON Schema 2020-12

import Ajv2020 from "ajv/dist/2020";
import addFormats from "ajv-formats";

const ajv = new Ajv2020({ allErrors: true });
addFormats(ajv);

12. Validating in Python with jsonschema

The jsonschema library is the standard JSON Schema validator for Python. It supports drafts 4, 6, 7, 2019-09, and 2020-12.

Installation

pip install jsonschema[format-nongpl]

The [format-nongpl] extra installs format validators (for email, uri, etc.) that use non-GPL dependencies.

Basic validation

import jsonschema
from jsonschema import validate, ValidationError, FormatChecker

schema = {
    "type": "object",
    "properties": {
        "name":  {"type": "string", "minLength": 1},
        "email": {"type": "string", "format": "email"},
        "age":   {"type": "integer", "minimum": 0}
    },
    "required": ["name", "email"]
}

data = {"name": "Alice", "email": "alice@example.com", "age": 28}

try:
    validate(instance=data, schema=schema, format_checker=FormatChecker())
    print("Valid!")
except ValidationError as e:
    print(f"Validation error: {e.message}")
    print(f"Path: {list(e.absolute_path)}")
    print(f"Schema path: {list(e.absolute_schema_path)}")

Collecting all errors

By default, validate() raises on the first error. To collect all errors, use a Validator object directly:

from jsonschema import Draft202012Validator

validator = Draft202012Validator(schema, format_checker=FormatChecker())
errors = list(validator.iter_errors(data))

for error in sorted(errors, key=lambda e: e.path):
    print(f"  - {'.'.join(str(p) for p in error.path)}: {error.message}")

13. Common Schema Mistakes

Wrong type names

JSON Schema type names are lowercase strings. "type": "String", "type": "Integer", and "type": "Boolean" are all invalid — the correct names are "string", "integer", and "boolean".

Forgetting required

Listing a property in properties does not make it mandatory. A schema with "properties": {"name": {"type": "string"}} happily validates an empty object {}. You must add "required": ["name"] to enforce that the property is present.

The additionalProperties trap

Setting "additionalProperties": false is strict — it blocks any property not listed in properties. This causes unexpected failures when you use allOf to compose schemas, because each sub-schema only knows about its own properties. The safe pattern for composition is to avoid additionalProperties: false at the allOf level, or use unevaluatedProperties: false (2019-09+) which is composition-aware.

Using definitions instead of $defs

The key definitions was a convention in draft-04 through draft-07, but it was never an official keyword — validators just happened to support it as a base URI for $ref. From 2019-09 onward, $defs is the official keyword. Use $defs for new schemas and ensure your validator supports the draft version you're using.

Assuming number validates integers

In JSON Schema, "type": "integer" is a sub-type of "number". An integer value like 5 passes both "type": "number" and "type": "integer". But 5.0 passes "type": "number" and fails "type": "integer". If your API receives floats that happen to be whole numbers, decide upfront which type you actually need.

14. JSON Schema vs TypeScript Types

Both JSON Schema and TypeScript describe the shape of data, but they serve different purposes and work at different points in the development pipeline:

AspectJSON SchemaTypeScript Types
When it runsRuntime validationCompile-time type checking
LanguageLanguage-agnostic JSONTypeScript only
Validates external dataYesNo (types are erased at runtime)
Regex constraintsYes (pattern)No (template literal types only)
Range constraintsYes (minimum, maximum)No
Generates documentationYesWith tools (TSDoc)
Stored in version controlJSON file.ts file

TypeScript cannot validate data coming from a network request, a database, or a file at runtime — types are completely erased during compilation. JSON Schema is what you need to validate data at your API's entry point. The two are complementary, not competing.

Generating one from the other

Several tools bridge the gap between JSON Schema and TypeScript:

The most maintainable approach for large TypeScript projects is to define schemas in Zod or write JSON Schema files and generate TypeScript interfaces from them — keeping the schema as the source of truth and the TypeScript types as a derived artifact.

15. Frequently Asked Questions

What is JSON Schema used for? +
JSON Schema is used to describe and validate the structure of JSON data. It defines what properties are required, what types values must be, and what constraints apply. Developers use it for API request and response validation, configuration file validation, form input validation, data pipeline integrity checks, and auto-generating documentation or client SDKs.
What is the difference between JSON Schema draft-07 and 2020-12? +
Draft-07 introduced if/then/else conditional validation and readOnly/writeOnly annotations. JSON Schema 2020-12 restructured the specification into multiple vocabulary documents, changed $recursiveRef to $dynamicRef, made $defs the official keyword for reusable definitions, and changed how prefixItems works for tuple validation. For most projects in 2026, draft-07 or 2019-09 remain the best-supported options across popular validator libraries.
How do I validate JSON Schema in JavaScript? +
Use the Ajv library. Install it with npm install ajv ajv-formats, then compile your schema with const validate = ajv.compile(schema) and call validate(data). If it returns false, check validate.errors for the list of validation failures. Each error includes the path to the failing value, the keyword that failed, and a human-readable message.
Can JSON Schema validate email addresses and URLs? +
Yes, using the format keyword with values like "email", "uri", "date", or "ipv4". However, format validation is optional by default in most validators. In Ajv, enable it by installing the ajv-formats package and calling addFormats(ajv). In Python's jsonschema library, pass format_checker=jsonschema.FormatChecker() to the validate function.
What is the difference between anyOf and oneOf in JSON Schema? +
anyOf validates if the data is valid against at least one of the listed sub-schemas (logical OR). oneOf validates if the data is valid against exactly one of the listed sub-schemas — no more, no less (exclusive OR). Use anyOf when multiple schemas can legitimately apply simultaneously. Use oneOf when the sub-schemas are mutually exclusive, such as when you have a discriminated union type where only one branch should match.

16. Related Tools

JSON Schema Validator
Validate JSON against any schema online
JSON Schema Generator
Generate a schema from sample JSON
JSON Validator
Validate JSON syntax instantly
JSON to TypeScript
Generate TypeScript interfaces from JSON
JSON to Zod
Generate Zod schemas from JSON