JSON Schema unevaluatedProperties & unevaluatedItems Explained
unevaluatedProperties and unevaluatedItems are JSON Schema keywords introduced in Draft 2019-09 that solve a long-standing limitation of additionalProperties and additionalItems. When using allOf, anyOf, oneOf, or if/then/else, additionalProperties only considers properties defined in the same schema object — it cannot see properties defined in subschemas. unevaluatedProperties: false closes this gap by restricting all properties not evaluated by any applicable subschema, regardless of where in the schema tree they were defined. A schema with allOf: [{properties: {name: {}}}] plus additionalProperties: false incorrectly rejects name (it's in a subschema, not visible to additionalProperties). Replace with unevaluatedProperties: false and name is correctly allowed. Both keywords were introduced in JSON Schema Draft 2019-09 (also called Draft 8) and are supported by Ajv 8 and JSON Schema Draft 2020-12. This guide explains the difference from additionalProperties, when to use each, how unevaluatedProperties works with allOf and if/then, and implementation with Ajv validation. For background on schema structure see the JSON Schema guide.
Need to validate a JSON Schema using unevaluatedProperties? Jsonic's validator handles Draft 2019-09 and 2020-12 schemas instantly.
Open JSON Schema ValidatorThe Problem with additionalProperties and allOf
additionalProperties: false is one of the most commonly used JSON Schema keywords — and one of the most commonly misused. It works correctly in flat schemas but silently breaks when you add allOf, anyOf, or if/then/else. This is the single most common JSON Schema bug when combining schemas, and it affects every validator that follows the spec strictly, including Ajv 6 and 7.
The root cause: additionalProperties only considers properties and patternProperties defined in the same schema object. Properties defined inside subschemas — even those directly nested in allOf — are completely invisible to it. Here is a schema that incorrectly rejects a valid object:
// BROKEN: additionalProperties cannot see into allOf subschemas
{
"allOf": [
{
"properties": {
"name": { "type": "string" }
}
}
],
"additionalProperties": false
}
// Test cases:
// { "name": "Alice" } → REJECTED (incorrect — name is valid!)
// { "name": "Alice", "x": 1} → REJECTED (correct)
// {} → ALLOWED (correct)The object {"name":"Alice"} is rejected because additionalProperties: false at the root only sees 0 properties declared in properties at the root level. It considers name an additional property and rejects it. The subschema in allOf declaring name has no effect on the root-level additionalProperties check. This affects Draft 4, Draft 6, and Draft 7 — all 3 of the most widely deployed JSON Schema versions before 2019-09. The fix is to replace additionalProperties: false with unevaluatedProperties: false.
unevaluatedProperties: How It Works
unevaluatedProperties applies to every property not evaluated (visited and passing) by any keyword in scope: properties, patternProperties, additionalProperties, or any subschema via allOf / anyOf / oneOf / if / then / else / $ref. A property is considered "evaluated" if it was visited and passed validation in any of those subschemas. Only properties that remain un-evaluated after all subschemas are processed are subject tounevaluatedProperties.
Here are 2 schemas and 2 test cases that illustrate the difference. Schema 1 (broken with additionalProperties):
// Schema 1: BROKEN
{
"allOf": [{ "properties": { "name": { "type": "string" } } }],
"additionalProperties": false
}
// { "name": "Alice" } → FAIL (additionalProperties rejects name)
// { "name": "Alice", "x": 1 } → FAIL (both schemas reject it)Schema 2 (correct with unevaluatedProperties):
// Schema 2: CORRECT
{
"allOf": [{ "properties": { "name": { "type": "string" } } }],
"unevaluatedProperties": false
}
// { "name": "Alice" } → PASS (name evaluated by allOf subschema)
// { "name": "Alice", "x": 1 } → FAIL (x is unevaluated — correctly rejected)The evaluation model works in 2 passes. Pass 1: run all subschemas (allOf, anyOf, oneOf, if, then, else, $ref) and track which properties were visited and passed. Pass 2: unevaluatedProperties checks every property in the instance against the evaluated set. Properties in the evaluated set are allowed; properties not in the set are subject to the unevaluatedProperties schema (which is false to disallow them, or a schema to constrain them). This 2-pass model is why unevaluatedProperties requires a more sophisticated validator — Ajv 6 and 7 only implement the single-pass model. Use validate JSON Schema in JavaScript for a complete guide to setting up Ajv 8.
unevaluatedProperties with allOf (Schema Composition)
The primary use case for unevaluatedProperties is building "extended" types via allOf/anyOf/oneOf composition. A base schema defines 3 common fields shared by all instances of a type; an extension schema adds 2 more fields specific to a subtype; the combined schema uses unevaluatedProperties: false to reject anything beyond those 5 fields. This is "closed inheritance" — the equivalent of a sealed class in TypeScript or Kotlin.
// Base schema: 3 common fields (could live in a $ref or separate file)
const baseSchema = {
"type": "object",
"properties": {
"id": { "type": "string" },
"createdAt": { "type": "string", "format": "date-time" },
"updatedAt": { "type": "string", "format": "date-time" }
},
"required": ["id", "createdAt"]
}
// Extension schema: 2 additional fields
const userExtension = {
"type": "object",
"properties": {
"email": { "type": "string", "format": "email" },
"role": { "type": "string", "enum": ["admin", "member", "viewer"] }
},
"required": ["email", "role"]
}
// Combined schema: allOf + unevaluatedProperties: false
const userSchema = {
"allOf": [baseSchema, userExtension],
"unevaluatedProperties": false
}
// Test cases:
// { "id": "1", "createdAt": "2026-01-01T00:00:00Z",
// "email": "a@b.com", "role": "admin" } → PASS (all 5 fields evaluated)
// { "id": "1", "createdAt": "2026-01-01T00:00:00Z",
// "email": "a@b.com", "role": "admin",
// "extra": "x" } → FAIL (extra is unevaluated)This pattern replaces the broken approach of putting additionalProperties: false in each subschema (which prevents extension) or at the root (which incorrectly rejects subschema-defined properties). With unevaluatedProperties: false at the root of the combined schema only, all 5 fields from the 2 subschemas are correctly evaluated and allowed, while any 6th field is rejected. This is the standard pattern for building a library of reusable base schemas. For schemas using $ref, the same rule applies — properties declared inside a $ref target are evaluated and allowed by unevaluatedProperties: false.
unevaluatedProperties with if/then/else
When using if/then/else conditionals, additionalProperties: false ignores fields defined in the then or else branches — they are in subschemas and invisible to the root check. unevaluatedProperties: false evaluates all 3 branches (if, then, else) and correctly allows the fields that were validated by the applicable branch.
Consider a discriminated union where a type field controls which additional fields are allowed. Only admin users may have a permissions array:
{
"type": "object",
"properties": {
"type": { "type": "string", "enum": ["admin", "member"] },
"name": { "type": "string" }
},
"required": ["type", "name"],
"if": {
"properties": { "type": { "const": "admin" } }
},
"then": {
"properties": {
"permissions": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["permissions"]
},
"unevaluatedProperties": false
}
// Test cases:
// { "type": "admin", "name": "Alice",
// "permissions": ["read", "write"] } → PASS (permissions evaluated by then)
// { "type": "member", "name": "Bob" } → PASS (no extra fields)
// { "type": "admin", "name": "Alice",
// "permissions": ["read"],
// "secret": "x" } → FAIL (secret is unevaluated)
// { "type": "member", "name": "Bob",
// "permissions": [] } → FAIL (permissions not evaluated for member)With additionalProperties: false instead, the first test case would fail — permissions is in the then subschema, invisible to the root's additionalProperties check. unevaluatedProperties: false applies the 2-pass evaluation: the if condition fires for the admin type, the then branch evaluates permissions, and it is marked as evaluated — correctly passing. This pattern replaces manual discriminated union implementations in 3 lines of schema instead of complex oneOf with repeated base field declarations.
unevaluatedItems for Arrays
unevaluatedItems is the array equivalent of unevaluatedProperties, introduced in Draft 2019-09. It replaces additionalItems, which only worked when items was an array (tuple mode) and suffered the same subschema-blindness problem as additionalProperties. In Draft 2020-12, the array-of-schemas form of items was renamed to prefixItems, and unevaluatedItems: false is the correct way to close a tuple.
// Draft 2020-12 tuple: exactly [string, number], nothing else
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "array",
"prefixItems": [
{ "type": "string" },
{ "type": "number" }
],
"unevaluatedItems": false
}
// Test cases:
// ["Alice", 42] → PASS (2 items, both match prefixItems)
// ["Alice", 42, true] → FAIL (3rd item is unevaluated)
// ["Alice"] → PASS (only 1 prefix item provided — unevaluatedItems
// only restricts items BEYOND the prefix)unevaluatedItems: false also works with contains. Array positions matched by contains are marked as evaluated; any remaining positions are subject to unevaluatedItems. This enables schemas like "the array must contain at least 1 string, and every other item must also be a string":
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "array",
"contains": { "type": "string" },
"unevaluatedItems": { "type": "string" }
}
// [1, "a"] → FAIL (1 is unevaluated and not a string)
// ["a", "b"] → PASS (all items are strings; contains is satisfied)Ajv 8 with { draft2020: true } (or importing from ajv/dist/2020) supports both prefixItems and unevaluatedItems. Ajv 8 without the 2020 option supports unevaluatedItems against the Draft 2019-09 definition where items as an array still serves as the prefix. See Ajv validation for full Ajv 8 setup instructions.
Key terms
- unevaluatedProperties
- A JSON Schema keyword (Draft 2019-09+) that restricts object properties not evaluated by any keyword in the schema tree, including those in
allOf,anyOf,oneOf,if/then/else, and$refsubschemas. - additionalProperties
- A JSON Schema keyword that restricts object properties not declared in
propertiesorpatternPropertieswithin the same schema object only — it does not look into subschemas, making it unsuitable for use with schema composition. - unevaluatedItems
- The array equivalent of
unevaluatedProperties(Draft 2019-09+), restricting array items not evaluated byprefixItems,items, orcontainsin any applicable subschema. - prefixItems
- A Draft 2020-12 keyword that defines tuple positions in an array (replacing the array form of
items), where each entry is a schema that applies to the corresponding index; items beyond the prefix are subject tounevaluatedItemsoritems. - evaluated property
- A property in a JSON object instance that was visited and passed validation in at least one applicable keyword (
properties,patternProperties,additionalProperties, or any subschema via composition keywords), making it exempt from theunevaluatedPropertiescheck. - closed schema
- A schema that disallows any properties or items not explicitly declared, typically achieved with
unevaluatedProperties: false(for objects) orunevaluatedItems: false(for arrays), acting as a "closed world" constraint for strict API validation. - Draft 2019-09
- The JSON Schema specification revision (also called Draft 8) that introduced
unevaluatedPropertiesandunevaluatedItems, along with$anchor,$recursiveRef, and vocabulary support — the first draft to support proper schema composition closing.
Frequently asked questions
What is the difference between additionalProperties and unevaluatedProperties?
additionalProperties only considers properties and patternProperties defined in the same schema object. unevaluatedProperties considers properties evaluated anywhere in the schema tree — including allOf, anyOf, oneOf, if/then/else, and $ref subschemas. If you have an allOf that defines a name property in a subschema and then set additionalProperties: false at the root, name will be incorrectly rejected because the root schema cannot see into the subschema. unevaluatedProperties: false sees across all subschemas and correctly allows name. Use unevaluatedProperties: false to close a composed schema; use additionalProperties: false only when you have no subschemas. See the JSON Schema guide for foundational context.
When should I use unevaluatedProperties instead of additionalProperties?
Always use unevaluatedProperties when the schema uses allOf, anyOf, oneOf, if/then/else, or $ref. Using additionalProperties: false with these keywords is a common mistake that rejects valid data — any property defined inside a subschema is invisible to the root additionalProperties check. Using unevaluatedProperties: false in these cases correctly accounts for all properties defined across every subschema in scope. Use additionalProperties: false only in flat schemas with no composition. See allOf/anyOf/oneOf composition for more on combining schemas correctly.
Does Ajv support unevaluatedProperties?
Ajv 8+ supports unevaluatedProperties natively with new Ajv() or by setting the JSON Schema draft explicitly. Ajv 6 and 7 do not support unevaluatedProperties — you must upgrade. Install with npm install ajv@8. To use Draft 2019-09 features with Ajv 8, import Ajv2019 from ajv/dist/2019 or use the default Ajv constructor which also supports it. JSON Schema validators that support Draft 2019-09 or later — including ajv 8 and jsonschema 1.4+ — all support unevaluatedProperties. Validators that only implement Draft 7 or earlier will silently ignore the keyword or throw an error. See the full Ajv validation guide for setup details.
What JSON Schema draft introduced unevaluatedProperties?
Draft 2019-09 (sometimes called Draft 8) introduced unevaluatedProperties. It was a major addition that resolved the additionalProperties + composition problem that existed in Draft 4, 6, and 7. The limitation had been known for years: combining allOf with additionalProperties: false was essentially broken for composed schemas. Draft 2019-09 introduced both unevaluatedProperties (for objects) and unevaluatedItems (for arrays) in the same release. Draft 2020-12 retained and refined both keywords. Any schema using $schema: https://json-schema.org/draft/2019-09/schema or later can use these keywords. For Draft 2020-12 specifics see the Draft 2020-12 guide.
What is unevaluatedItems?
unevaluatedItems is the array equivalent of unevaluatedProperties, introduced in Draft 2019-09. It restricts array items not evaluated by prefixItems (Draft 2020-12), items, or contains. unevaluatedItems: false means no items beyond those defined in prefixItems or matched by contains are allowed — useful for tuple schemas with conditional extra items. In Draft 2020-12, the items keyword was renamed to prefixItems for tuple definitions, and unevaluatedItems: false is the correct way to close that tuple. Ajv 8 with draft2020: true supports both prefixItems and unevaluatedItems.
Can I use unevaluatedProperties: false as a default schema style?
Yes, and it is recommended for production schemas. Setting unevaluatedProperties: false at the root of every schema prevents accidental extra properties from passing validation. It acts as a "closed world" constraint — only explicitly declared properties are allowed. The tradeoff is that every expected property must be explicitly declared in properties, patternProperties, or a subschema reachable via allOf/anyOf/oneOf/if/then/else/$ref. This is generally desirable for strict API validation: it catches typos in field names, prevents schema drift, and makes the contract explicit. Use validate JSON Schema in JavaScript to test schemas with Ajv 8 in your project.
Ready to validate schemas with unevaluatedProperties?
Use Jsonic's JSON Schema Validator to test schemas using unevaluatedProperties and unevaluatedItems with Draft 2019-09 and 2020-12 support.