JSON Schema Validation Keywords: Numbers, Strings, Arrays
Last updated:
JSON Schema validation keywords are the building blocks that constrain what values are acceptable for each data type. The 3 most searched keywords — minimum, maximum, and minLength — cover only a fraction of what's available. This reference covers every validation keyword for numbers, strings, arrays, and objects, with working examples and the critical Draft-04 vs Draft-07 behavior changes that break existing schemas.
Each keyword is either an assertion (causes validation to fail when the condition is not met) or an annotation (records metadata without failing). Most keywords in this guide are assertions. The format keyword is an annotation by default in most validators — it only fails validation when explicitly configured to do so.
Number Keywords: minimum, maximum, multipleOf
Number keywords apply to "type": "number" and "type": "integer" schemas. There are 5 assertion keywords: minimum, maximum, exclusiveMinimum, exclusiveMaximum, and multipleOf.
| Keyword | Type | Inclusive? | Example |
|---|---|---|---|
minimum | number | Yes — value ≥ minimum | { "minimum": 0 } — accepts 0, 1, 100 |
maximum | number | Yes — value ≤ maximum | { "maximum": 100 } — accepts 0, 50, 100 |
exclusiveMinimum | number (Draft-07+) | No — value > exclusiveMinimum | { "exclusiveMinimum": 0 } — rejects 0, accepts 0.001 |
exclusiveMaximum | number (Draft-07+) | No — value < exclusiveMaximum | { "exclusiveMaximum": 1 } — rejects 1, accepts 0.999 |
multipleOf | positive number | N/A | { "multipleOf": 0.25 } — accepts 0, 0.25, 0.5, 1.0 |
// Age: integer between 0 and 120 (inclusive)
{
"type": "integer",
"minimum": 0,
"maximum": 120
}
// Probability: number strictly between 0 and 1
{
"type": "number",
"exclusiveMinimum": 0,
"exclusiveMaximum": 1
}
// Price: non-negative, max 2 decimal places
{
"type": "number",
"minimum": 0,
"multipleOf": 0.01
}
// Percentage: 0–100, multiples of 0.5 (e.g. 50.0, 50.5, 99.5)
{
"type": "number",
"minimum": 0,
"maximum": 100,
"multipleOf": 0.5
}minimum and maximum can be combined freely. All four range keywords are independent assertions — a value must satisfy every one that is present. The order in which they appear in the schema document has no effect on validation.
String Keywords: minLength, maxLength, pattern
String keywords apply only when the instance is a string. They are silently ignored (not an error) when the instance is a number, boolean, or other type — unless "type": "string" is also present.
| Keyword | Value type | Counts | Common use |
|---|---|---|---|
minLength | non-negative integer | Unicode code points | Require non-empty strings, minimum username length |
maxLength | non-negative integer | Unicode code points | Database column limits, API field caps |
pattern | string (ECMA-262 regex) | N/A — substring match by default | Format enforcement (slugs, IDs, phone numbers) |
// Username: 3–20 characters, lowercase letters, numbers, underscores
{
"type": "string",
"minLength": 3,
"maxLength": 20,
"pattern": "^[a-z0-9_]+$"
}
// Non-empty string (common field requirement)
{
"type": "string",
"minLength": 1
}
// Email with length cap (RFC 5321 max is 254)
{
"type": "string",
"format": "email",
"maxLength": 254
}
// URL slug: lowercase, hyphens, no leading/trailing hyphens
{
"type": "string",
"pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"
}
// UUID v4 (fully anchored)
{
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
}
// Hex color code
{
"type": "string",
"pattern": "^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
}The Unicode code point counting rule means "maxLength": 10 allows a string of 10 emojis even though they occupy 40 UTF-8 bytes. If you need to enforce a byte limit (for example, a database column measured in bytes), validate the byte length in application code — JSON Schema has no byte-length keyword.
String format Keyword
format is defined by the JSON Schema specification but is classified as an annotation, not an assertion, in Draft-07 through Draft 2020-12. This means "format": "email" does not cause validation to fail by default — validators annotate the value for documentation purposes only. To enable format enforcement in Ajv, pass { formats: require("ajv-formats") } after installing the ajv-formats package.
// Standard format values (Draft 2019-09+)
{ "type": "string", "format": "date-time" } // "2026-05-19T12:00:00Z"
{ "type": "string", "format": "date" } // "2026-05-19"
{ "type": "string", "format": "time" } // "12:00:00Z"
{ "type": "string", "format": "duration" } // "P1Y2M3D"
{ "type": "string", "format": "email" } // "user@example.com"
{ "type": "string", "format": "idn-email" } // internationalized email
{ "type": "string", "format": "hostname" } // "api.example.com"
{ "type": "string", "format": "ipv4" } // "192.168.1.1"
{ "type": "string", "format": "ipv6" } // "::1"
{ "type": "string", "format": "uri" } // "https://example.com/path"
{ "type": "string", "format": "uri-reference" }// "/path?q=1" (relative allowed)
{ "type": "string", "format": "uuid" } // "550e8400-e29b-41d4-a716-446655440000"
{ "type": "string", "format": "json-pointer" } // "/foo/bar/0"
{ "type": "string", "format": "regex" } // "^[a-z]+$"
// Enabling format validation in Ajv
import Ajv from "ajv"
import addFormats from "ajv-formats"
const ajv = new Ajv()
addFormats(ajv)
// Now format: "email" actively rejects non-email stringsCustom format keywords can be registered with ajv.addFormat("phone", /^\+[1-9]\d{1,14}$/). This is preferable to pattern when the same format is reused across many schemas — define once, reference everywhere by name.
Array Keywords: minItems, maxItems, uniqueItems, contains
Array keywords govern the length, uniqueness, and item membership of JSON arrays. They only apply when the instance is an array.
| Keyword | Value type | Draft | Effect |
|---|---|---|---|
minItems | non-negative integer | Draft-04+ | Array must have at least N items |
maxItems | non-negative integer | Draft-04+ | Array must have at most N items |
uniqueItems | boolean | Draft-04+ | All items must be unique (deep equality) |
contains | schema | Draft-06+ | At least 1 item must match the given schema |
minContains | non-negative integer | Draft 2019-09+ | Minimum number of items matching contains |
maxContains | non-negative integer | Draft 2019-09+ | Maximum number of items matching contains |
// Tags array: 1–10 unique strings, each 1–50 characters
{
"type": "array",
"items": { "type": "string", "minLength": 1, "maxLength": 50 },
"minItems": 1,
"maxItems": 10,
"uniqueItems": true
}
// Batch request: 1–100 items
{
"type": "array",
"items": { "$ref": "#/$defs/RequestItem" },
"minItems": 1,
"maxItems": 100
}
// Mixed array that must contain at least one number
{
"type": "array",
"contains": { "type": "number" }
}
// Array with exactly 2–4 strings (minContains + maxContains, Draft 2019-09+)
{
"type": "array",
"contains": { "type": "string" },
"minContains": 2,
"maxContains": 4
}
// Permission list: must include "read", allows others
{
"type": "array",
"items": { "type": "string" },
"contains": { "const": "read" },
"uniqueItems": true
}Object Keywords: minProperties, maxProperties
Two keywords constrain the number of properties present in a JSON object: minProperties and maxProperties. They count all keys, including those not listed in properties, regardless of whether additionalProperties is set.
// PATCH body — must send at least 1 field, cannot send unknown fields
{
"type": "object",
"properties": {
"name": { "type": "string", "minLength": 1 },
"email": { "type": "string", "format": "email" },
"bio": { "type": "string", "maxLength": 500 }
},
"minProperties": 1,
"additionalProperties": false
}
// Open extensible object, but cap at 20 keys to prevent abuse
{
"type": "object",
"properties": {
"id": { "type": "string" },
"name": { "type": "string" }
},
"required": ["id", "name"],
"additionalProperties": { "type": "string" },
"maxProperties": 20
}
// Dynamic metadata map: 0–50 string key-value pairs
{
"type": "object",
"additionalProperties": { "type": "string" },
"maxProperties": 50
}minProperties: 1 combined with additionalProperties: false is the standard pattern for PATCH endpoints — it rejects empty objects while still allowing any combination of the defined optional fields. Without minProperties: 1, an empty {} would pass validation even though it contains no update instructions.
Draft-04 vs Draft-07 Breaking Changes
The most common migration issue is the exclusiveMinimum and exclusiveMaximum behavior change between Draft-04 and Draft-07. Schemas using the Draft-04 boolean form silently produce wrong validation results when run against a Draft-07 validator without updating the $schema declaration.
// Draft-04 form — exclusiveMinimum is a boolean flag
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "number",
"minimum": 0,
"exclusiveMinimum": true // means: strictly greater than minimum (0)
}
// Draft-07 form — exclusiveMinimum is a standalone number
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "number",
"exclusiveMinimum": 0 // minimum: 0 is no longer needed
}
// Both express: value must be > 0
// Ajv uses your $schema declaration to determine which form to expect.
// If $schema is missing or wrong, validation silently uses incorrect semantics.
// DANGER — this is valid Draft-04 but wrong in Draft-07:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "number",
"minimum": 0,
"exclusiveMinimum": true // In Draft-07, this is IGNORED (boolean is not a number)
// Result: minimum: 0 is treated as inclusive — value 0 passes incorrectly
}Always include an explicit $schema URI in your schemas. Ajv defaults to Draft-2020-12 when no $schema is present. If you load schemas from an API or third-party library, inspect the $schema field before assuming the exclusive keyword form.
| Feature | Draft-04 | Draft-07 / 2019-09 / 2020-12 |
|---|---|---|
exclusiveMinimum | boolean (modifier on minimum) | number (standalone keyword) |
exclusiveMaximum | boolean (modifier on maximum) | number (standalone keyword) |
definitions | definitions keyword | $defs (Draft 2019-09+); definitions still works |
contains | Not available | Added in Draft-06 |
if/then/else | Not available | Added in Draft-07 |
format enforcement | Assertion (validators vary) | Annotation by default; opt-in assertion |
Combining Validation Keywords
All validation keywords within a schema are evaluated independently. A value must satisfy every keyword that applies. Combining keywords from different categories creates precise, multi-dimensional constraints.
// Complete user registration schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"username": {
"type": "string",
"minLength": 3,
"maxLength": 30,
"pattern": "^[a-zA-Z0-9_]+$"
},
"email": {
"type": "string",
"format": "email",
"maxLength": 254
},
"age": {
"type": "integer",
"minimum": 13,
"maximum": 120
},
"score": {
"type": "number",
"minimum": 0.0,
"maximum": 1.0,
"multipleOf": 0.01
},
"tags": {
"type": "array",
"items": { "type": "string", "minLength": 1, "maxLength": 30 },
"minItems": 0,
"maxItems": 5,
"uniqueItems": true
},
"roles": {
"type": "array",
"items": { "enum": ["admin", "editor", "viewer"] },
"minItems": 1,
"uniqueItems": true,
"contains": { "const": "viewer" }
},
"metadata": {
"type": "object",
"additionalProperties": { "type": "string" },
"maxProperties": 10
}
},
"required": ["username", "email"],
"minProperties": 2
}When using enum alongside number range keywords, enum takes precedence — only the listed values are valid regardless of minimum/maximum. Combine them only when both constraints serve a purpose, such as enum for allowed port numbers within a range.
Definitions
- Annotation
- A JSON Schema keyword that attaches metadata to an instance without causing validation to fail.
format,title,description, anddefaultare all annotations. They are collected by validators as output and can be used by documentation generators and form builders, but do not affect the pass/fail result of validation unless explicitly configured to do so. - Assertion
- A JSON Schema keyword that causes validation to fail when its condition is not satisfied.
minimum,maxLength,pattern,uniqueItems, andrequiredare all assertions. The distinction matters when understanding why adding"format": "email"to a schema does not immediately reject bad email strings without additional validator configuration. - Exclusive minimum
- A boundary constraint that excludes the boundary value itself. A value is valid only if it is strictly greater than the exclusive minimum. In JSON Schema Draft-07 and later, expressed as
{ "exclusiveMinimum": 0 }— a standalone number. In Draft-04, it was a boolean flag alongsideminimum:{ "minimum": 0, "exclusiveMinimum": true }. The two forms are mutually incompatible; mixing them without the correct$schemadeclaration causes silent validation errors. - ECMA-262 regex
- The regular expression dialect defined by the ECMAScript specification (used in JavaScript). JSON Schema's
patternkeyword uses this dialect. Key characteristics: no implicit anchoring (a pattern matches any substring unless anchored with^and$), supports character classes, quantifiers, and alternation. Lookbehind assertions ((?<=...)) are defined in ES2018 and supported in modern Ajv but may not be available in older validators. Possessive quantifiers and atomic groups (found in PCRE) are not part of ECMA-262. - Unicode code point
- The numeric identifier assigned to each character in the Unicode standard, ranging from U+0000 to U+10FFFF. JSON Schema's
minLengthandmaxLengthmeasure string length in code points, not bytes. A standard ASCII character is 1 code point and 1 UTF-8 byte. A 4-byte emoji (e.g., 😀 is U+1F600) is 1 code point and 4 UTF-8 bytes. A Chinese character is typically 1 code point and 3 UTF-8 bytes. This means a 10-character limit enforced viamaxLength: 10allows strings up to 40 bytes if all characters are 4-byte emojis.
FAQ
What is the difference between minimum and exclusiveMinimum in JSON Schema?
minimum is inclusive — the boundary value passes validation. exclusiveMinimum is exclusive — the boundary value fails. For probability scores that must be strictly positive, use { "exclusiveMinimum": 0 } rather than { "minimum": 1 }. The critical migration note: in Draft-04, exclusiveMinimum was a boolean flag used alongside minimum. From Draft-07 onward, it became a standalone number. Always set an explicit $schema URI so validators apply the correct interpretation.
Does minLength count characters or bytes in JSON Schema?
Unicode code points, not bytes. A 4-byte emoji like 😀 counts as 1 toward minLength/maxLength. This means "maxLength": 5 allows "Hi 😀!" (5 code points: H, i, space, 😀, !) even though it is 8 UTF-8 bytes. When your database column is measured in bytes (e.g., VARCHAR(255) in MySQL with utf8mb4 encoding can hold 63 4-byte characters), enforce the byte limit in application code separately from the JSON Schema validation.
How do I use the pattern keyword correctly in JSON Schema?
Always anchor your patterns with ^ (start) and $ (end) unless you intentionally want substring matching. Without anchors, "pattern": "[0-9]+" passes the string "abc123def" because the substring "123" matches. With anchors, "pattern": "^[0-9]+$" rejects "abc123def" correctly. A common mistake is writing "pattern": "^slug-[a-z]+" without the trailing $ — this passes "slug-abc!!!INVALID" because the regex matches the beginning substring.
What format values are built into JSON Schema?
The specification defines: date-time, date, time, duration, email, idn-email, hostname, idn-hostname, ipv4, ipv6, uri, uri-reference, iri, iri-reference, uuid, json-pointer, relative-json-pointer, and regex. These are annotations by default — they document intent but do not reject invalid values unless the validator is configured with format enforcement. Ajv requires the ajv-formats package and either addFormats(ajv) or the format: 'fast' option.
How does multipleOf work with decimal numbers in JSON Schema?
multipleOf accepts any positive number including decimals. { "multipleOf": 0.01 } validates currency values with up to 2 decimal places — 9.99 passes, 9.999 fails. Watch out for floating-point precision: 1.05 / 0.01 in IEEE 754 arithmetic is 104.99999999999999, not 105. Ajv applies a tolerance to handle this correctly, but other validators may not. When precision is critical, represent monetary values as integers (cents) in your data model and validate with "type": "integer" instead.
How do I validate that an array has no duplicate items in JSON Schema?
Set "uniqueItems": true. Duplicates are detected by deep equality — [{"id":1}, {"id":1}] is rejected, but [{"id":1,"role":"admin"}, {"id":1,"role":"user"}] passes because the objects differ. If you need uniqueness on a specific field only (e.g., unique by id regardless of other fields), JSON Schema has no built-in keyword for that — validate programmatically or register a custom Ajv keyword.
What is the contains keyword in JSON Schema arrays?
contains requires at least 1 item in the array to match a given schema, without constraining other items. { "contains": { "const": "admin" } } accepts ["viewer", "admin", "editor"] and rejects ["viewer", "editor"]. Pair with minContains and maxContains (Draft 2019-09+) to require a specific count: { "contains": { "type": "string" }, "minContains": 2, "maxContains": 3 } requires exactly 2 or 3 strings in the array.
How do minProperties and maxProperties work for JSON Schema objects?
They count the total number of keys present, not just keys listed in properties. "minProperties": 1 rejects {} and is the standard guard for PATCH endpoints where an empty body is meaningless. "maxProperties": 20 combined with "additionalProperties": { "type": "string" } caps how many arbitrary extension fields a client can submit — useful when you allow open-ended metadata but need to prevent payload bloat.
Further reading and primary sources
- JSON Schema Reference: Keywords — Official JSON Schema keyword reference
- Ajv Validation — Ajv supported keywords and validation behavior