← All articles

What Is JSON Schema? Validation & Examples

What JSON Schema is, how validation works, the core keywords, draft 2020-12 examples, Ajv/Python validation, and mistakes to avoid.

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.
  • id must be a string matching the order ID pattern.
  • customerEmail is intended to have email format.
  • totalCents must be a non-negative integer.
  • currency must be one of three allowed values.
  • items must be a non-empty array.
  • Each item must have sku and quantity.
  • 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: true in 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:

  1. Check that the files are valid JSON syntax.
  2. 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-schema can infer a JSON Schema from example JSON.
  • Python's genson can 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:

  • required fields.
  • enum values.
  • minimum and maximum.
  • minLength and maxLength.
  • additionalProperties policy.
  • format annotations or assertions.
  • oneOf branches 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

Sources

Last reviewed July 2026.