{ }jsonkitOpen

JSON Schema: a complete guide with examples

7 min read · updated 2026-07-12

JSON Schema is a declarative language that describes the shape of JSON data. You write one schema that lists which fields exist, which are required, what types they hold and what values are allowed. Any conforming validator can check data against it and return consistent, machine-readable errors. You no longer need hand-written validation in every service.

This guide covers the current dialect, draft 2020-12. It goes through the core keywords, the boolean combinators (oneOf, anyOf, allOf, not) and reuse with $ref and $defs. It ends with a full schema, validation examples in JavaScript and Python, and how to generate a schema from existing data.

What JSON Schema is and why it exists

A JSON Schema is a JSON document. Its keywords set constraints on the data it describes. For example, a value must be a string, an object must have a name property, or an array's items must be numbers between 0 and 100. When a validator checks an instance (the data) against a schema, it returns success or a list of errors that name the constraints that failed.

You get one source of truth for validation. One schema can check an HTTP API's request body, drive form validation in the browser, generate documentation, produce fake data for tests and scaffold TypeScript types. You write the rules once instead of copying fragile if checks across a codebase.

  • API contracts. Reject malformed request and response payloads before they reach business logic.
  • Configuration files. Validate a service config at startup and fail early with clear messages.
  • Data pipelines. Check records before they enter a store or queue, so bad data never lands.
  • Tooling. Editors like VS Code use JSON Schema for autocomplete and inline errors in JSON and YAML files.

Draft 2020-12 and the $schema keyword

JSON Schema has versions called drafts. The current, widely supported one is draft 2020-12. Older documents often use draft-07. The core keywords are nearly the same, but 2020-12 changed how arrays and references work in a few important ways. Always declare your dialect with the $schema keyword so validators read keywords correctly.

A minimal but complete schema. $schema names the dialect, and $id gives the schema a stable identity.json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.com/schemas/user.json",
  "title": "User",
  "type": "object"
}

$id gives the schema a base URI. Other schemas point at it with $ref, and relative references inside the document resolve against it. title and description are annotations. They don't constrain data, but tools show them in docs and error messages, so they are worth writing.

Core JSON Schema keywords

Most schemas use a small set of keywords, grouped here by the type of value they apply to.

Type and object structure

KeywordApplies toWhat it does
typeanyRestricts the JSON type: string, number, integer, boolean, object, array or null. Can be an array of types.
propertiesobjectMaps property names to subschemas that their values must satisfy.
requiredobjectLists property names that must be present (independent of properties).
additionalPropertiesobjectControls properties not named in properties. false forbids them, and a schema constrains them.
itemsarraySchema every array element must match.
enumanyThe value must equal one of a fixed list of allowed values.
constanyThe value must equal exactly this one value. Same as an enum with one item.

Use const to pin a field to a single value, such as "version": { "const": 2 } or a type tag like "kind": { "const": "card" }. The comparison is by JSON value, so { "const": 1 } accepts 1 and 1.0 but rejects "1". const is also how you build a discriminator for oneOf, shown below.

String, number and array constraints

KeywordApplies toWhat it does
minLength / maxLengthstringBounds on string length in characters.
patternstringA regular expression the string must match (unanchored by default).
formatstringSemantic hint like email, date-time, uri or uuid. Enforced only if the validator opts in.
minimum / maximumnumberInclusive numeric bounds. Use exclusiveMinimum / exclusiveMaximum for strict bounds.
multipleOfnumberThe number must be an exact multiple of this value.
minItems / maxItemsarrayBounds on array length.
uniqueItemsarrayWhen true, all array elements must be distinct.

Combine schemas with oneOf, anyOf, allOf and not

The four combinators apply boolean logic to subschemas. They let JSON Schema model unions, mixins and mutual exclusion.

  • allOf: the data must match every subschema. Use it to add constraints to a base schema, a form of inheritance.
  • anyOf: the data must match at least one subschema. Good for loose unions where overlap is fine.
  • oneOf: the data must match exactly one subschema. Use it when the options are mutually exclusive, like a discriminated union.
  • not: the data must not match the subschema. Useful for exclusions, such as "any string except these reserved words."
A payment method that is exactly one of card or bank transfer, distinguished by a const discriminator.json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "oneOf": [
    {
      "properties": {
        "kind": { "const": "card" },
        "cardNumber": { "type": "string", "pattern": "^[0-9]{16}$" }
      },
      "required": ["kind", "cardNumber"]
    },
    {
      "properties": {
        "kind": { "const": "bank" },
        "iban": { "type": "string" }
      },
      "required": ["kind", "iban"]
    }
  ]
}

Reuse with $ref and $defs

Schemas often repeat structures, such as an address, a timestamp or a money amount. Instead of copying them, declare each once under $defs and point to it with $ref. A $ref is a URI reference. The fragment #/$defs/address is a JSON Pointer into the current document.

An address subschema defined once and referenced twice.json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.com/order.json",
  "type": "object",
  "properties": {
    "billingAddress": { "$ref": "#/$defs/address" },
    "shippingAddress": { "$ref": "#/$defs/address" }
  },
  "required": ["billingAddress"],
  "$defs": {
    "address": {
      "type": "object",
      "properties": {
        "street": { "type": "string" },
        "city": { "type": "string" },
        "postalCode": { "type": "string" }
      },
      "required": ["street", "city"]
    }
  }
}

$defs is the standard place for reusable subschemas. It replaced the older definitions keyword. References can also point to other documents. "$ref": "https://example.com/address.json" loads a schema by its $id. Recursive structures like trees work too, since a $ref can point back to the schema that contains it.

A full schema and validation example

Here is a complete user schema that uses most of the keywords above, followed by validation in JavaScript and Python.

user.schema.json, a realistic object schema.json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.com/user.schema.json",
  "title": "User",
  "type": "object",
  "properties": {
    "id": { "type": "string", "format": "uuid" },
    "email": { "type": "string", "format": "email" },
    "age": { "type": "integer", "minimum": 0, "maximum": 150 },
    "role": { "enum": ["admin", "editor", "viewer"] },
    "tags": {
      "type": "array",
      "items": { "type": "string" },
      "uniqueItems": true
    }
  },
  "required": ["id", "email", "role"],
  "additionalProperties": false
}

In JavaScript, Ajv is the standard validator. Note the ajv-formats plugin. Without it, format is ignored.

Validating with Ajv (npm install ajv ajv-formats).js
import Ajv from "ajv/dist/2020.js";
import addFormats from "ajv-formats";
import schema from "./user.schema.json" with { type: "json" };

const ajv = new Ajv({ allErrors: true });
addFormats(ajv);
const validate = ajv.compile(schema);

const user = {
  id: "7d4e0f1a-1b2c-4d3e-8f90-abcdef123456",
  email: "dev@example.com",
  role: "editor",
  tags: ["beta"],
};

if (validate(user)) {
  console.log("valid");
} else {
  console.log(validate.errors);
}
The same check in Python with the jsonschema package (pip install jsonschema).python
import json
from jsonschema import Draft202012Validator

with open("user.schema.json") as f:
    schema = json.load(f)

validator = Draft202012Validator(schema)
user = {
    "id": "7d4e0f1a-1b2c-4d3e-8f90-abcdef123456",
    "email": "dev@example.com",
    "role": "editor",
}

errors = sorted(validator.iter_errors(user), key=lambda e: e.path)
for error in errors:
    print(f"{list(error.path)}: {error.message}")
if not errors:
    print("valid")

additionalProperties is false, so an unexpected field like isAdmin: true produces a clear error instead of passing quietly. That is what you want when you protect an API against overposting.

Generate a JSON Schema from JSON

Writing a schema by hand for a large payload is tedious. It is faster to generate a JSON Schema from a representative JSON sample and then tighten it. Paste your JSON into the generator below. It returns a draft 2020-12 schema with types, properties and required fields filled in.

JSON Schema GeneratorOpen the tool→

Treat the output as a starting point, not a finished contract. An inferred schema is only as good as its sample. It can't know that age should be capped at 150, that role is an enum or that additionalProperties should be false. Review it and add the constraints one example can't show, such as bounds, patterns, enums and formats. If your JSON is messy, tidy it with the JSON Formatter and check that it parses with the JSON Validator first.

Frequently asked questions

What is the difference between JSON Schema draft-07 and draft 2020-12?

They share nearly all core keywords. 2020-12 changed array validation: tuples moved from items to prefixItems, and items now applies to every remaining element. It also made $defs the standard instead of definitions. Declare your dialect with $schema so validators apply the right rules, and check that your validator supports 2020-12 before upgrading.

What does const mean in JSON Schema?

const requires the value to equal one specific JSON value. { "const": "card" } accepts only the string "card". It works like an enum with a single item, and it is the usual way to write a discriminator field for oneOf. The comparison is by JSON value, so 1 and 1.0 are equal but "1" is not.

Does JSON Schema validate the format keyword automatically?

Not by default. format (email, date-time, uuid, uri and others) is treated as an annotation unless the validator opts into format assertion. In Ajv you must install and register ajv-formats. Python's jsonschema checks some formats only when optional libraries are installed. If a value must match a format, turn checking on or add a pattern as a backup.

When should I use oneOf versus anyOf?

Use anyOf when the data may match one or more of the options. It passes if at least one matches. Use oneOf only when the options are mutually exclusive and exactly one should match. It fails if none or more than one match. Overlapping oneOf branches cause confusing errors, so add a const discriminator to keep them separate.

How do I forbid extra properties in a JSON object?

Set additionalProperties: false on the object schema. It rejects any property not listed under properties (or matched by patternProperties). It is the most important keyword for locking down API payloads and preventing overposting. It also means you must list every allowed field.

Can I reuse the same subschema in multiple places?

Yes. Define it once under $defs and reference it with $ref and a JSON Pointer like #/$defs/address. References can point inside the same document or to another schema by its $id. They also support recursion, so you can describe trees and linked lists.

How do I create a JSON Schema from an existing JSON file?

Generate a starting schema from a representative sample with a tool like the JSON Schema Generator. It outputs draft 2020-12 with types, properties and required fields filled in. Then refine it by hand. Add enums, numeric bounds, patterns, formats and additionalProperties: false, since one example can't capture those rules.