What Is JSON Schema?

JSON Schema is a JSON document that describes the expected structure of another JSON document. It defines types, required fields, value constraints, and nested shapes. A validator reads both the schema and the input and tells you: valid or not, and exactly why not.

The spec lives at json-schema.org. There have been several draft versions; Draft 07 is the most widely supported by libraries, while Draft 2020-12 is the current stable standard. The two are largely compatible for everyday use cases.

The smallest valid schema is an empty object {}, which accepts any JSON value. The most common starting point looks like this:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "age":  { "type": "integer", "minimum": 0 }
  },
  "required": ["name"]
}

This schema accepts any JSON object that has a name string. The age field is optional, but if present it must be a non-negative integer.

Core Keywords Reference

The following table covers the keywords you'll use in 90% of schemas:

Keyword Applies to What it does
typeanyConstrains the JSON type: string, number, integer, boolean, array, object, null
propertiesobjectDefines a map of property name → sub-schema. Properties not listed are still allowed by default.
requiredobjectArray of property names that must be present.
additionalPropertiesobjectfalse rejects keys not in properties. A schema sub-object constrains the type of extra keys.
itemsarraySchema that every element of the array must satisfy.
minLength / maxLengthstringCharacter count bounds.
patternstringECMAScript regex the string must match.
minimum / maximumnumberNumeric bounds (inclusive). Use exclusiveMinimum / exclusiveMaximum for strict bounds.
enumanyValue must be one of the listed items: "enum": ["red","green","blue"]
constanyValue must equal exactly this: "const": "active"
$refanyReferences another schema by URI. Enables reuse and composition.
if / then / elseanyConditional validation: apply then when if passes, else otherwise.

Validating JSON in JavaScript with AJV

AJV (Another JSON Schema Validator) is the dominant choice for JavaScript and Node.js. It supports Draft 07, 2019-09, and 2020-12, and it compiles schemas to optimized JavaScript functions so validation is extremely fast even at scale.

Install

npm install ajv

Basic usage

import Ajv from 'ajv';

const ajv = new 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: 'Ada', email: 'ada@example.com', age: 36 };

if (validate(data)) {
  console.log('Valid!');
} else {
  console.error(validate.errors);
  // [{ instancePath: '/email', message: 'must match format "email"', ... }]
}
Tip: format validation needs ajv-formats. AJV does not validate format keywords by default. Install npm install ajv-formats and call addFormats(ajv) before compiling to enable "email", "uri", "date", "uuid", and others.

TypeScript: inferring types from schemas

AJV integrates cleanly with TypeScript via type guards. Compile your schema once, then use the returned validator as a type predicate:

import Ajv, { JSONSchemaType } from 'ajv';

interface User {
  name:  string;
  email: string;
  age?:  number;
}

const schema: JSONSchemaType<User> = {
  type: 'object',
  properties: {
    name:  { type: 'string' },
    email: { type: 'string' },
    age:   { type: 'number', nullable: true },
  },
  required: ['name', 'email'],
};

const ajv = new Ajv();
const validate = ajv.compile<User>(schema);

function processUser(raw: unknown): User {
  if (!validate(raw)) throw new Error(JSON.stringify(validate.errors));
  return raw; // TypeScript now knows raw is User
}

Writing a Real-World Schema

Most real schemas validate API request bodies. Here's a complete schema for a product order:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$defs": {
    "Address": {
      "type": "object",
      "properties": {
        "street":  { "type": "string" },
        "city":    { "type": "string" },
        "country": { "type": "string", "minLength": 2, "maxLength": 2 }
      },
      "required": ["street", "city", "country"],
      "additionalProperties": false
    }
  },
  "type": "object",
  "properties": {
    "orderId":   { "type": "string", "pattern": "^ORD-[0-9]{6}$" },
    "items": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "object",
        "properties": {
          "sku":      { "type": "string" },
          "quantity": { "type": "integer", "minimum": 1 }
        },
        "required": ["sku", "quantity"],
        "additionalProperties": false
      }
    },
    "shippingAddress": { "$ref": "#/$defs/Address" },
    "billingAddress":  { "$ref": "#/$defs/Address" }
  },
  "required": ["orderId", "items", "shippingAddress"],
  "additionalProperties": false
}

Notice the $defs section. The Address schema is defined once and referenced twice via $ref. Changing the address format in one place updates both references automatically.

Composition: allOf, anyOf, oneOf

JSON Schema provides three keywords for combining schemas:

Extending a base schema with allOf

const BaseEntity = {
  type: 'object',
  properties: {
    id:        { type: 'string' },
    createdAt: { type: 'string', format: 'date-time' },
  },
  required: ['id', 'createdAt'],
};

const ProductSchema = {
  allOf: [
    BaseEntity,
    {
      type: 'object',
      properties: {
        name:  { type: 'string' },
        price: { type: 'number', minimum: 0 },
      },
      required: ['name', 'price'],
    },
  ],
};

Discriminated unions with oneOf

{
  "oneOf": [
    {
      "properties": {
        "type":  { "const": "email" },
        "email": { "type": "string" }
      },
      "required": ["type", "email"]
    },
    {
      "properties": {
        "type":  { "const": "sms" },
        "phone": { "type": "string" }
      },
      "required": ["type", "phone"]
    }
  ]
}

An object with type: "email" must have email. An object with type: "sms" must have phone. Anything else is rejected. This pattern is the JSON Schema equivalent of a TypeScript discriminated union.

Draft Versions Comparison

Feature Draft 04 Draft 07 Draft 2020-12
if / then / elseNoYesYes
$defs (was definitions)Nodefinitions$defs
$dynamicRefNoNoYes
readOnly / writeOnlyNoYesYes
contentMediaTypeNoYesYes
Library supportDecliningWidestGrowing
Rule of thumb: Start with Draft 07 unless your library explicitly defaults to a newer draft. Draft 07 has the broadest ecosystem support across Python, Go, Ruby, and Java — not just JavaScript.

Common Mistakes

1. Forgetting additionalProperties: false

Without it, extra fields are silently allowed. An API that accepts {"name":"Alice","admin":true} when only name was defined is a security risk. Close your objects explicitly.

2. Confusing required with presence

required: ["status"] means the key must exist, but the value can be null unless you also constrain the type. Use "type": "string" alongside required to prevent null values.

3. Using number when you mean integer

JSON Schema's "type": "number" accepts both 1 and 1.5. Use "type": "integer" when decimal values should be rejected. A quantity field that accepts 2.7 units is a silent bug waiting to happen.

4. Pattern anchoring

JSON Schema regex patterns are not anchored by default. The pattern "[0-9]+" matches any string containing at least one digit — including "abc123def". Wrap patterns in anchors: "^[0-9]+$".

Validating JSON in the Browser

You can run AJV in the browser as well as Node.js. But for quick debugging and ad-hoc validation during development, our JSON Validator at json-format.dev supports JSON Schema Draft 07 validation directly in your browser. Paste your schema and data, and you get instant error reporting with precise field paths — no installation required.

Validate your JSON against a schema right now — no setup, no signup, 100% in-browser.

Try JSON Schema Validator

Frequently Asked Questions

What is JSON Schema?
JSON Schema is a vocabulary for annotating and validating JSON documents. It is itself a JSON document that describes the expected shape, types, required fields, and constraints of another JSON document. The current stable version is Draft 2020-12; Draft 07 is the most widely supported by libraries.
What is JSON Schema validation?
JSON Schema validation is the process of checking whether a JSON document conforms to a schema. A validator reads the schema and the input document, then returns pass/fail plus a list of errors describing exactly which fields failed and why.
What is the best JSON Schema validator for JavaScript?
AJV (Another JSON Schema Validator) is the most widely used validator for JavaScript and Node.js. It supports Draft 07, Draft 2019-09, and Draft 2020-12. It compiles schemas to optimized JavaScript functions, making it extremely fast. Zod is a popular alternative that generates schemas from TypeScript types.
What is the difference between JSON Schema Draft 07 and Draft 2020-12?
Draft 07 is the last version under the old numbering scheme and remains the most supported. Draft 2020-12 introduced $dynamicRef, changed how $id and $anchor work, and restructured vocabulary declarations. For most projects, Draft 07 is sufficient and has broader library support.
What does additionalProperties: false do?
It rejects any JSON object that contains keys not listed in the properties keyword, making the schema "closed." Without it, extra properties are silently allowed — a common source of schema leakage bugs.
How do I make a field optional but still type-checked?
Omit the field from the required array while keeping it in properties. The validator only enforces the type constraint when the field is present. The document is valid whether the optional field appears or not.
What is $ref in JSON Schema?
$ref references another schema by URI, enabling reuse. $ref: '#/$defs/Address' points to the Address schema defined in the $defs section of the same document. External URIs are also supported.
What is the difference between anyOf, oneOf, and allOf?
allOf: document must be valid against ALL listed schemas. anyOf: document must be valid against AT LEAST ONE. oneOf: document must be valid against EXACTLY ONE. Use allOf for extension, anyOf for unions, and oneOf for discriminated variants.
Can JSON Schema validate nested objects?
Yes. Use the properties keyword recursively — each property value is a full sub-schema. For arrays of objects, use items to define the schema each array element must satisfy.
Does JSON Schema support conditional validation?
Yes, via if/then/else (added in Draft 07). If the document passes the if schema, the then schema is also enforced. If it fails if, the else schema applies. This enables cross-field rules like: if paymentType is "credit_card", then cardNumber is required.