← Back to Blog Developer Tools

JSON Schema Complete Guide: Validation, Types, References & TypeScript (2026)

🛠️ JSON Schema Tools — 100% Client-Side, Instant Results

1. What is JSON Schema?

JSON Schema is a declarative vocabulary for describing the structure and constraints of JSON data. A JSON Schema is itself a JSON document that defines what another JSON document should look like — what properties it must have, what types they must be, what values are allowed, and which fields are required.

JSON Schema solves a real problem in API-first development: JSON is completely permissive by nature. Any key, any value, any nesting depth is valid JSON. Without a schema, there is no machine-readable contract between the producer and consumer of an API. JSON Schema provides that contract.

What JSON Schema Is Used For

  • API request validation — reject malformed requests at the gateway before they reach your business logic.
  • API response validation — ensure your API always returns what it promises (contract testing).
  • Form validation — libraries like AJV validate form inputs server-side in milliseconds.
  • Configuration file validation — VS Code uses JSON Schema for IntelliSense and validation in tsconfig.json, package.json, and similar files.
  • TypeScript type generation — convert schemas to TypeScript interfaces automatically, keeping runtime and compile-time types in sync.
  • Documentation — OpenAPI 3.x uses JSON Schema as its schema language for documenting API shapes.

2. Draft Versions: Which to Use?

JSON Schema has evolved through several draft versions. Use Draft 2020-12 for new projects:

Version$schema URIStatusKey Changes
Draft 4http://json-schema.org/draft-04/schema#LegacyFoundation; definitions, allOf/anyOf/oneOf
Draft 6http://json-schema.org/draft-06/schema#Legacyconst, contains, propertyNames
Draft 7http://json-schema.org/draft-07/schema#Widely usedif/then/else, readOnly/writeOnly
Draft 2019-09https://json-schema.org/draft/2019-09/schemaStable$defs replaces definitions, $recursiveRef
Draft 2020-12https://json-schema.org/draft/2020-12/schema✅ CurrentprefixItems, $dynamicRef, unevaluatedProperties

$schema at the top of your schema tells validators which draft to use. Always include it. The most widely supported combination today is Draft 7 (AJV default) or 2020-12 (AJV with ajv/dist/2020). Check your validator's supported drafts before choosing.

3. Basic Types and Type-Specific Keywords

JSON Schema defines 7 primitive types, mapping directly to JSON's native types:

TypeJSON ExampleKey Validation Keywords
string"hello"minLength, maxLength, pattern, format, enum, const
number3.14minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf
integer42Same as number; value must have no fractional part
booleantrueconst: true / const: false
nullnullOnly matches JSON null
object{"key": "val"}properties, required, additionalProperties, minProperties, maxProperties, patternProperties
array[1, 2, 3]items, prefixItems, contains, minItems, maxItems, uniqueItems

Nullable Types

A field can accept multiple types by passing an array to type:

// Nullable string (string or null):
{ "type": ["string", "null"] }

// In Draft 2020-12, you can also use anyOf:
{ "anyOf": [{ "type": "string" }, { "type": "null" }] }

enum and const

// enum: value must be one of these exactly
{ "type": "string", "enum": ["pending", "active", "deleted"] }

// const: value must be exactly this (a single-value enum)
{ "const": "active" }

4. Object Validation: properties, required, additionalProperties

Here is a complete annotated user schema demonstrating all major object keywords:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.com/schemas/user.json",
  "title": "User",
  "description": "A registered user of the application",
  "type": "object",
  "required": ["id", "email", "createdAt"],
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid",
      "description": "Unique user identifier"
    },
    "email": {
      "type": "string",
      "format": "email",
      "maxLength": 320
    },
    "name": {
      "type": ["string", "null"],
      "minLength": 1,
      "maxLength": 100
    },
    "age": {
      "type": "integer",
      "minimum": 0,
      "maximum": 150
    },
    "role": {
      "type": "string",
      "enum": ["admin", "user", "moderator"]
    },
    "tags": {
      "type": "array",
      "items": { "type": "string" },
      "uniqueItems": true,
      "maxItems": 20
    },
    "createdAt": {
      "type": "string",
      "format": "date-time"
    }
  },
  "additionalProperties": false
}

additionalProperties: false — Strict Mode

By default, JSON Schema allows objects to have any properties beyond those declared in properties. Setting additionalProperties: false makes the schema strict — extra properties cause validation failure. This is equivalent to TypeScript's exact object type checking and is critical for catching typos in property names.

patternProperties — Property Name Patterns

{
  "type": "object",
  "patternProperties": {
    "^x-": { "type": "string" }  // any property starting with x- must be a string
  },
  "additionalProperties": false
}

unevaluatedProperties (Draft 2020-12)

unevaluatedProperties is like additionalProperties but it understands composition keywords (allOf, if/then/else). Use it when combining schemas with allOf where each schema declares some properties — additionalProperties: false on the combined schema would incorrectly reject properties from the sub-schemas.

5. Array Validation: items, prefixItems, contains

// Homogeneous array: all items must match the same schema
{
  "type": "array",
  "items": { "type": "string", "format": "email" },
  "minItems": 1,
  "maxItems": 100,
  "uniqueItems": true   // no duplicates
}

// Tuple (Draft 2020-12): each position has a specific type
// (was 'items' as array in older drafts)
{
  "type": "array",
  "prefixItems": [
    { "type": "string" },    // position 0: string
    { "type": "number" },    // position 1: number
    { "type": "boolean" }    // position 2: boolean
  ],
  "items": false             // no additional items beyond the 3 defined
}

// contains: at least one item must match
{
  "type": "array",
  "contains": { "type": "string", "format": "email" },
  "minContains": 1,          // at least 1 email required
  "maxContains": 5           // at most 5 emails
}

6. $ref and $defs: Reusable Schemas

$ref is JSON Schema's equivalent of TypeScript type aliases. Instead of repeating a complex schema in multiple places, define it once in $defs (or definitions in older drafts) and reference it:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.com/schemas/order.json",
  "title": "Order",
  "type": "object",
  "required": ["orderId", "customer", "items", "total"],
  "properties": {
    "orderId": { "type": "string", "format": "uuid" },
    "customer": { "$ref": "#/$defs/Customer" },
    "items": {
      "type": "array",
      "items": { "$ref": "#/$defs/LineItem" },
      "minItems": 1
    },
    "total": { "$ref": "#/$defs/Money" },
    "status": {
      "type": "string",
      "enum": ["pending", "processing", "shipped", "delivered", "cancelled"]
    }
  },
  "$defs": {
    "Customer": {
      "type": "object",
      "required": ["id", "email"],
      "properties": {
        "id": { "type": "string" },
        "email": { "type": "string", "format": "email" },
        "name": { "type": "string" }
      }
    },
    "LineItem": {
      "type": "object",
      "required": ["productId", "quantity", "unitPrice"],
      "properties": {
        "productId": { "type": "string" },
        "quantity": { "type": "integer", "minimum": 1 },
        "unitPrice": { "$ref": "#/$defs/Money" }
      }
    },
    "Money": {
      "type": "object",
      "required": ["amount", "currency"],
      "properties": {
        "amount": { "type": "number", "minimum": 0 },
        "currency": { "type": "string", "pattern": "^[A-Z]{3}$" }
      }
    }
  }
}

External $ref

$ref can point to an external schema file or URL:

// Reference an external schema file:
{ "$ref": "https://example.com/schemas/address.json" }

// Reference another local file:
{ "$ref": "./address.schema.json" }

// Reference a specific definition in another file:
{ "$ref": "./types.schema.json#/$defs/Money" }

7. Schema Composition: allOf, anyOf, oneOf, not

// allOf — must match ALL schemas (intersection, like TypeScript &)
{
  "allOf": [
    { "$ref": "#/$defs/BaseEntity" },
    { "$ref": "#/$defs/HasTimestamps" },
    {
      "type": "object",
      "properties": {
        "email": { "type": "string", "format": "email" }
      }
    }
  ]
}

// anyOf — must match AT LEAST ONE schema (union, like TypeScript |)
{
  "anyOf": [
    { "type": "string", "format": "email" },
    { "type": "string", "format": "uri" }
  ]
}

// oneOf — must match EXACTLY ONE schema (exclusive union)
{
  "oneOf": [
    {
      "type": "object",
      "properties": { "type": { "const": "card" }, "cardNumber": { "type": "string" } },
      "required": ["type", "cardNumber"]
    },
    {
      "type": "object",
      "properties": { "type": { "const": "paypal" }, "paypalEmail": { "type": "string" } },
      "required": ["type", "paypalEmail"]
    }
  ]
}

// not — must NOT match the schema
{
  "not": { "type": "null" }
}
KeywordLogicTypeScript EquivalentUse Case
allOfAND — all must passA & BExtending base schemas, mixins
anyOfOR — at least one must passA | BUnion types, flexible input
oneOfXOR — exactly one must passDiscriminated unionMutually exclusive variants
notNOT — must not matchExclude<T, U>Blacklisting values or types

Discriminated Unions with oneOf

The payment method example in the code above is a discriminated union — a common API pattern where a type field determines which properties are required. JSON Schema handles this cleanly with oneOf + const. Tools like AJV validate these efficiently using the discriminator field.

8. Conditional Schemas: if/then/else

Draft 7+ supports conditional validation: if one condition passes, apply one set of rules; otherwise apply another. This avoids duplicating schemas in multiple oneOf branches when only a few fields differ:

// if/then/else — conditional validation
{
  "type": "object",
  "properties": {
    "paymentMethod": { "type": "string" },
    "cardNumber": { "type": "string" },
    "bankAccountNumber": { "type": "string" }
  },
  "if": {
    "properties": { "paymentMethod": { "const": "credit_card" } },
    "required": ["paymentMethod"]
  },
  "then": {
    "required": ["cardNumber"]
  },
  "else": {
    "required": ["bankAccountNumber"]
  }
}

dependentRequired and dependentSchemas

// dependentRequired: if 'cardNumber' is present, 'cvv' is also required
{
  "dependentRequired": {
    "cardNumber": ["cvv", "expiryMonth", "expiryYear"]
  }
}

// dependentSchemas: if 'type' is present, apply additional schema
{
  "dependentSchemas": {
    "creditCard": {
      "properties": { "creditLimit": { "type": "number" } },
      "required": ["creditLimit"]
    }
  }
}

9. String Formats

The format keyword provides semantic validation for strings. Note: in JSON Schema, format validation is optional — validators may or may not enforce it by default. AJV requires explicit opt-in with { "formats": "full" }.

FormatDescriptionExample
date-timeRFC 3339 datetime2026-06-19T10:30:00Z
dateISO 8601 date2026-06-19
timeISO 8601 time10:30:00
durationISO 8601 durationP1Y2M3DT4H
emailEmail address[email protected]
idn-emailInternationalized email用户@例子.广告
uriURIhttps://example.com/path
uri-referenceURI or relative reference/relative/path
uuidUUID v1–v5550e8400-e29b-41d4-a716-446655440000
ipv4IPv4 address192.168.1.1
ipv6IPv6 address2001:db8::1
hostnameDNS hostnameapi.example.com
json-pointerJSON Pointer/properties/name

10. Annotations: title, description, default, examples

Annotation keywords do not affect validation — they add human-readable documentation and tooling hints:

{
  "type": "object",
  "title": "Product",
  "description": "A product in the catalog",
  "properties": {
    "price": {
      "type": "number",
      "title": "Price",
      "description": "Price in USD, inclusive of tax",
      "default": 0,
      "examples": [9.99, 24.99, 99.0],
      "minimum": 0
    },
    "sku": {
      "type": "string",
      "title": "SKU",
      "description": "Stock keeping unit identifier",
      "pattern": "^[A-Z]{2}-[0-9]{6}$",
      "examples": ["AB-123456"]
    }
  }
}

OpenAPI 3.x, VS Code IntelliSense, Swagger UI, and documentation generators all consume these annotations to build human-readable docs from your schema — no manual documentation needed.

11. Generating TypeScript from JSON Schema

One of the most powerful uses of JSON Schema is automatic TypeScript type generation. This keeps your runtime validation (AJV schema) and compile-time types (TypeScript interfaces) perfectly in sync — a single source of truth.

// Input JSON Schema (simplified):
// { type: "object", properties: { id: {type:"string"}, age: {type:"integer"} } }

// Generated TypeScript (from json-schema-to-typescript or our tool):
export interface User {
  id: string;
  email: string;
  name?: string | null;
  age?: number;
  role?: "admin" | "user" | "moderator";
  tags?: string[];
  createdAt: string;
  [k: string]: unknown; // if additionalProperties not false
}

// With $defs, nested types are extracted:
export interface Order {
  orderId: string;
  customer: Customer;
  items: LineItem[];
  total: Money;
  status?: "pending" | "processing" | "shipped" | "delivered" | "cancelled";
}
export interface Customer { id: string; email: string; name?: string; }
export interface LineItem { productId: string; quantity: number; unitPrice: Money; }
export interface Money { amount: number; currency: string; }

Our browser-only JSON Schema → TypeScript Generator handles this conversion entirely client-side — paste your JSON Schema and get TypeScript interfaces instantly, with support for $ref, $defs, composition keywords, and all Draft 7 and 2020-12 features.

Command-Line Generation (for CI/CD)

# Install json-schema-to-typescript:
npm install -g json-schema-to-typescript

# Generate TypeScript from a schema file:
json2ts --input user.schema.json --output user.ts

# Generate from multiple schemas (glob):
json2ts --input 'schemas/**/*.json' --output types/

AJV Type-Safe Validation

When using AJV with generated TypeScript types, you can get fully typed validation with a type guard:

import Ajv from "ajv/dist/2020";
import { User } from "./types/user";
import userSchema from "./schemas/user.schema.json";

const ajv = new Ajv({ strict: true });
const validate = ajv.compile<User>(userSchema);

function validateUser(data: unknown): User {
  if (!validate(data)) throw new Error(ajv.errorsText(validate.errors));
  return data; // TypeScript knows this is User
}

12. Using JSON Schema for API Validation

The most common production use of JSON Schema is validating API request bodies. Here's how to integrate schema validation at the framework level:

// Express.js with AJV middleware
import Ajv from "ajv/dist/2020";
import addFormats from "ajv-formats";

const ajv = new Ajv({ allErrors: true, strict: true });
addFormats(ajv); // add date-time, email, uuid format validation

function validateBody(schema: object) {
  const validate = ajv.compile(schema);
  return (req, res, next) => {
    if (!validate(req.body)) {
      return res.status(400).json({
        error: "Validation failed",
        details: ajv.errorsText(validate.errors, { separator: '; ' })
      });
    }
    next();
  };
}

// Use in route:
app.post('/users', validateBody(userSchema), createUserHandler);

Use our JSON Schema Validator to quickly test if sample API payloads pass your schema before writing any code. If you have sample JSON data but no schema yet, use our JSON → JSON Schema Generator to auto-generate a starting schema from your example data.

Frequently Asked Questions

What is JSON Schema?
JSON Schema is a vocabulary for annotating and validating JSON documents. It is a JSON document that describes the expected structure, types, constraints, and allowed values of another JSON document. It is used for API validation, configuration file validation, TypeScript type generation, and OpenAPI documentation.
What are the JSON Schema types?
JSON Schema defines 7 primitive types: string, number, integer, boolean, null, object, and array. Each type has its own validation keywords — strings have minLength/maxLength/pattern, numbers have minimum/maximum/multipleOf, objects have properties/required/additionalProperties, and arrays have items/minItems/maxItems/uniqueItems.
What is the difference between allOf, anyOf, and oneOf?
allOf requires data to be valid against ALL listed schemas (TypeScript & intersection). anyOf requires valid against AT LEAST ONE schema (TypeScript | union). oneOf requires valid against EXACTLY ONE schema (exclusive union — mutually exclusive). Use anyOf for "this OR that" and oneOf for discriminated union patterns where exactly one branch applies.
What is $ref in JSON Schema?
$ref is a JSON Pointer reference to another schema, enabling reuse. Define reusable schemas in the $defs section (Draft 2020-12) or definitions (older drafts) and reference them with "$ref": "#/$defs/MyType". This is equivalent to TypeScript type aliases and prevents duplicating complex schemas across a large schema file.
How do I generate TypeScript types from JSON Schema?
Use the json-schema-to-typescript npm package for command-line generation. Our browser-based JSON Schema → TypeScript Generator does this client-side instantly — paste your schema and get TypeScript interfaces with full support for $ref, $defs, composition keywords, and discriminated unions.
What is additionalProperties in JSON Schema?
additionalProperties controls whether an object can have undeclared properties. Setting it to false makes the schema strict — any property not listed in properties causes validation failure (equivalent to TypeScript's strict object types). Setting it to a schema allows additional properties but requires them to match that schema.