What Is JSON Schema? A Complete Guide with Examples

JSON Schema is a vocabulary that lets you describe the expected structure of a JSON document. You write a schema — itself a JSON object — and then any JSON document can be validated against it automatically. If the data matches the schema, it's valid. If it doesn't, you get detailed error messages telling you exactly what's wrong.

JSON Schema is used for API request and response validation, configuration file enforcement, form input validation, auto-generated documentation, and generating code (TypeScript interfaces, database models, form widgets). It is the foundation of OpenAPI and is supported by every major language.

The simplest valid schema

An empty object {} is a valid JSON Schema. It accepts any JSON value at all. In practice, you start adding keywords to restrict what values are valid.

{
  "$schema": "https://json-schema.org/draft-07/schema",
  "type": "object"
}

The $schema keyword declares which draft of JSON Schema you are using. It is optional but recommended — validators use it to choose the correct behaviour.

The type keyword

type restricts the kind of value allowed. Valid values are: "string", "number", "integer", "boolean", "array", "object", and "null". You can also pass an array to allow multiple types.

{ "type": "string" }          // only strings pass
{ "type": "integer" }         // only integers (no decimals)
{ "type": ["string", "null"] } // string or null (nullable field)

Describing objects: properties and required

properties defines the expected keys and their schemas. required is an array of key names that must be present. Any key not listed in properties is still allowed unless you add "additionalProperties": false.

{
  "type": "object",
  "properties": {
    "id":    { "type": "integer" },
    "name":  { "type": "string" },
    "email": { "type": "string", "format": "email" },
    "age":   { "type": "integer", "minimum": 0, "maximum": 120 }
  },
  "required": ["id", "name", "email"],
  "additionalProperties": false
}

This schema validates that:

String constraints

When type is "string", additional keywords constrain the value further:

{
  "type": "string",
  "minLength": 3,           // at least 3 characters
  "maxLength": 50,          // no more than 50 characters
  "pattern": "^[a-z0-9]+$", // only lowercase letters and digits (regex)
  "format": "email"         // well-known format hint (validated by some tools)
}

Common format values: "email", "uri", "date" (YYYY-MM-DD), "date-time" (ISO 8601), "uuid", "ipv4". Format validation is optional — validators may only treat it as a hint unless you configure strict format checking.

Restricting to a fixed set: enum

enum limits a value to one of a fixed list. It works on any type.

{
  "type": "string",
  "enum": ["active", "inactive", "pending", "banned"]
}

Number constraints

{
  "type": "number",
  "minimum": 0,            // value >= 0
  "maximum": 100,          // value <= 100
  "exclusiveMinimum": 0,   // value > 0 (draft-07 syntax)
  "multipleOf": 0.01       // must be a multiple of 0.01 (useful for currency)
}

Array validation: items, minItems, maxItems

items defines the schema every element in the array must match. Use minItems and maxItems to constrain the length, and uniqueItems: true to prohibit duplicates.

{
  "type": "array",
  "items": { "type": "string" },  // every element must be a string
  "minItems": 1,                   // array cannot be empty
  "maxItems": 10,                  // no more than 10 elements
  "uniqueItems": true              // no duplicate values
}

To validate arrays where each position has a different type (tuples), use prefixItems in draft 2020-12, or an array value for items in draft-07:

// draft-07 tuple: [string, number, boolean]
{
  "type": "array",
  "items": [
    { "type": "string" },
    { "type": "number" },
    { "type": "boolean" }
  ],
  "additionalItems": false
}

Nested objects

Schemas nest naturally — a property's schema can itself be a full schema with its own properties, required, and constraints.

{
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "address": {
      "type": "object",
      "properties": {
        "street": { "type": "string" },
        "city":   { "type": "string" },
        "zip":    { "type": "string", "pattern": "^\\d{5}$" }
      },
      "required": ["street", "city", "zip"]
    }
  },
  "required": ["name", "address"]
}

Reusing definitions with $defs

To avoid repeating the same sub-schema in multiple places, define it once under $defs and reference it with $ref.

{
  "$defs": {
    "nonEmptyString": {
      "type": "string",
      "minLength": 1
    }
  },
  "type": "object",
  "properties": {
    "firstName": { "$ref": "#/$defs/nonEmptyString" },
    "lastName":  { "$ref": "#/$defs/nonEmptyString" }
  },
  "required": ["firstName", "lastName"]
}

In draft-07, use definitions (without the dollar sign) instead of $defs.

A complete real-world example

This schema validates a user registration payload:

{
  "$schema": "https://json-schema.org/draft-07/schema",
  "title": "User Registration",
  "description": "Schema for a new user sign-up payload",
  "type": "object",
  "properties": {
    "username": {
      "type": "string",
      "minLength": 3,
      "maxLength": 30,
      "pattern": "^[a-zA-Z0-9_]+$"
    },
    "email": {
      "type": "string",
      "format": "email"
    },
    "password": {
      "type": "string",
      "minLength": 8
    },
    "age": {
      "type": "integer",
      "minimum": 13
    },
    "role": {
      "type": "string",
      "enum": ["user", "admin", "moderator"],
      "default": "user"
    },
    "tags": {
      "type": "array",
      "items": { "type": "string", "minLength": 1 },
      "maxItems": 5,
      "uniqueItems": true
    }
  },
  "required": ["username", "email", "password"],
  "additionalProperties": false
}

Valid input:

{
  "username": "alice_dev",
  "email": "alice@example.com",
  "password": "s3cr3tpass",
  "age": 25,
  "role": "user",
  "tags": ["javascript", "api"]
}

Invalid — would fail validation:

{
  "username": "al",              // too short — minLength: 3
  "email": "not-an-email",      // fails format: email
  "password": "abc",             // too short — minLength: 8
  "role": "superadmin",          // not in enum
  "extra_field": true            // additionalProperties: false
}

Validating in JavaScript with Ajv

Ajv is the fastest JSON Schema validator for JavaScript. Install it with npm install ajv.

import Ajv from 'ajv';
import addFormats from 'ajv-formats'; // for "email", "uri", "date-time" formats

const ajv = new Ajv();
addFormats(ajv);

const schema = {
  type: 'object',
  properties: {
    username: { type: 'string', minLength: 3 },
    email:    { type: 'string', format: 'email' }
  },
  required: ['username', 'email']
};

const validate = ajv.compile(schema);

const data = { username: 'alice', email: 'alice@example.com' };

if (validate(data)) {
  console.log('Valid ✓');
} else {
  console.error(validate.errors);
  // [{
  //   instancePath: '/email',
  //   message: 'must match format "email"',
  //   ...
  // }]
}

Each error object includes instancePath (which field failed), message (human-readable reason), and keyword (which schema keyword triggered it). This makes it straightforward to map errors back to form fields.

JSON Schema versions

JSON Schema has gone through several drafts. The most important ones:

Draft$schema URINotes
Draft-07https://json-schema.org/draft-07/schemaMost widely supported; use this as default
Draft 2019-09https://json-schema.org/draft/2019-09/schemaIntermediate; limited adoption
Draft 2020-12https://json-schema.org/draft/2020-12/schemaLatest; $defs, prefixItems; used by OpenAPI 3.1

OpenAPI 3.0 uses a modified subset of draft-07. OpenAPI 3.1 adopted full draft 2020-12. If you are writing schemas for an OpenAPI spec, check which version your spec uses.

Validate JSON against a schema online

Paste your JSON into the JSON Validator to check it for syntax errors first. For schema validation, the full JSON Schema Validator tool coming soon allows you to validate any JSON document against your schema directly in the browser.

Frequently Asked Questions

Why is my API rejecting data that looks valid to me?

APIs validate against a schema — not just syntax. Your JSON may be syntactically correct but semantically wrong: a string where an integer is expected, a missing required field, or a value outside the allowed enum. Use JSON Schema to define and check those rules locally first so you see the exact validation error before the API call.

Why does my schema validate in one tool but fail in another?

Different tools implement different JSON Schema draft versions. Keywords like $ref alongside sibling keywords, unevaluatedProperties, and $dynamicRef behave differently across drafts. Always declare "$schema": "https://json-schema.org/draft/2020-12/schema" at the top of your schema and use a validator that explicitly supports that draft.

How do I validate JSON against a schema in JavaScript?

Use the Ajv library: npm install ajv ajv-formats. Compile the schema with ajv.compile(schema), then call the compiled function with your data. It returns true or false, and errors are on validate.errors.

Is JSON Schema the same as OpenAPI?

No. OpenAPI uses JSON Schema to describe request and response bodies, but OpenAPI is a separate specification for documenting HTTP APIs. OpenAPI 3.1 adopted the full JSON Schema 2020-12 spec. OpenAPI 3.0 used a modified subset of draft-07 that excluded some keywords.