JSON Schema is a vocabulary for describing and validating the structure of JSON data. If you've ever wished you could enforce types on a JSON API response the way TypeScript enforces types in code, JSON Schema is the answer. This guide covers the core concepts you need to start validating JSON today.
What Is JSON Schema?
A JSON Schema is itself a JSON document that describes what valid data looks like. It defines types, required fields, value constraints, patterns, and relationships. Validators use the schema to check whether a given JSON document conforms.
Common use cases include:
- Validating API request and response bodies
- Documenting data formats (OpenAPI uses JSON Schema)
- Generating forms and UI from data models
- Configuration file validation (e.g., VS Code settings, ESLint config)
Your First Schema
Here's a simple schema for a user object:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": { "type": "string" },
"email": { "type": "string", "format": "email" },
"age": { "type": "integer", "minimum": 0 }
},
"required": ["name", "email"]
}This schema says: the data must be an object with name (string, required), email (string in email format, required), and age (non-negative integer, optional).
Types
JSON Schema supports seven primitive types:
| Type | Description |
|---|---|
string | Text values |
number | Any numeric value (integer or float) |
integer | Whole numbers only |
boolean | true or false |
object | Key-value pairs |
array | Ordered list of values |
null | The null value |
String Validation
Strings support length constraints, patterns, and built-in formats:
{
"type": "string",
"minLength": 1,
"maxLength": 255,
"pattern": "^[a-zA-Z0-9_]+$"
}
// Built-in formats (not all validators enforce these):
{ "type": "string", "format": "email" }
{ "type": "string", "format": "uri" }
{ "type": "string", "format": "date-time" }
{ "type": "string", "format": "uuid" }
{ "type": "string", "format": "ipv4" }
{ "type": "string", "format": "ipv6" }Number Validation
Numbers can be constrained to ranges and multiples:
{
"type": "number",
"minimum": 0,
"maximum": 100,
"exclusiveMinimum": 0,
"multipleOf": 0.01
}
// Integer with enum (only these values are valid)
{
"type": "integer",
"enum": [1, 2, 3, 5, 8, 13]
}Objects and Required Fields
Objects are defined by their properties. Use required to list fields that must be present, and additionalProperties to control whether extra fields are allowed:
{
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"role": {
"type": "string",
"enum": ["admin", "user", "viewer"]
}
},
"required": ["id", "name"],
"additionalProperties": false
}Setting additionalProperties: false makes the schema strict — any key not listed in properties will fail validation. This catches typos and prevents unexpected data from slipping through.
Arrays
Arrays are validated by the schema of their items, plus optional length and uniqueness constraints:
{
"type": "array",
"items": { "type": "string" },
"minItems": 1,
"maxItems": 10,
"uniqueItems": true
}
// Array of objects
{
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "integer" },
"tag": { "type": "string" }
},
"required": ["id", "tag"]
}
}Reuse with $ref and $defs
Avoid repeating yourself by defining reusable schemas with $defs and referencing them with $ref:
{
"$defs": {
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"zip": { "type": "string", "pattern": "^\\d{5}$" }
},
"required": ["street", "city", "zip"]
}
},
"type": "object",
"properties": {
"billing": { "$ref": "#/$defs/address" },
"shipping": { "$ref": "#/$defs/address" }
}
}The $ref pointer #/$defs/address means “look in the current document, under $defs, at the address key.” You can also reference schemas in external files.
Composition: oneOf, anyOf, allOf
Combine schemas with logical operators for more complex validation:
// Value must match exactly one schema
{
"oneOf": [
{ "type": "string" },
{ "type": "integer" }
]
}
// Value must match at least one schema
{
"anyOf": [
{ "type": "string", "minLength": 1 },
{ "type": "null" }
]
}
// Value must match ALL schemas (intersection)
{
"allOf": [
{ "$ref": "#/$defs/baseUser" },
{
"properties": {
"role": { "const": "admin" }
}
}
]
}Conditional Validation
Use if/then/else for fields that depend on other fields:
{
"type": "object",
"properties": {
"type": { "enum": ["email", "sms"] },
"value": { "type": "string" }
},
"if": {
"properties": { "type": { "const": "email" } }
},
"then": {
"properties": { "value": { "format": "email" } }
},
"else": {
"properties": { "value": { "pattern": "^\\+?[0-9]{10,15}$" } }
}
}If type is "email", the value must be a valid email. Otherwise, it must look like a phone number.
Real-World Example: API Response
Here's a complete schema for a paginated API response:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string", "minLength": 1 },
"email": { "type": "string", "format": "email" },
"createdAt": { "type": "string", "format": "date-time" }
},
"required": ["id", "name", "email", "createdAt"],
"additionalProperties": false
}
},
"meta": {
"type": "object",
"properties": {
"page": { "type": "integer", "minimum": 1 },
"perPage": { "type": "integer", "minimum": 1, "maximum": 100 },
"total": { "type": "integer", "minimum": 0 }
},
"required": ["page", "perPage", "total"]
}
},
"required": ["data", "meta"]
}Validating API payloads in production?
DigitalOcean App Platform deploys your Node.js, Python, or Go API from a Git repo with auto-scaling, free SSL, and built-in monitoring. Add JSON Schema validation to your endpoints and ship with confidence.
Validate Your Schemas
Use our JSON Schema Validator to test your schemas against sample data instantly. For generating TypeScript-style runtime validators, try the Zod Schema Generator. Both tools run entirely in your browser with no signup required.