Patterns by Keyword
Worked examples for each keyword family. All follow the Resolution Rules — this page shows them applied.
Logical Operators (anyOf / oneOf / allOf)
Index-accessed — use the indexed or array form, never a general string.
Per-Branch, Array Form
const schema = {
anyOf: [
{ type: "string", minLength: 5 },
{ type: "number", minimum: 100 },
],
errorMessage: {
anyOf: [
"String must have at least 5 characters",
"Number must be at least 100",
],
},
};Per-Branch, Object Form
const schema = {
anyOf: [
{ type: "string", minLength: 5 },
{ type: "number", minimum: 100 },
],
errorMessage: {
anyOf: {
0: "String must have at least 5 characters",
1: "Number must be at least 100",
},
},
};Per-Keyword Within Each Branch
const schema = {
anyOf: [
{ type: "string", minLength: 5, maxLength: 20 },
{ type: "number", minimum: 100 },
],
errorMessage: {
anyOf: [
{
_jetError: "Must be a valid string",
minLength: "String too short",
maxLength: "String too long",
},
{
_jetError: "Must be a valid number",
minimum: "Number too small",
},
],
},
};Conditionals (if / then / else)
if produces no errors. then and else are schema-accepting — they take a general string or drill in.
General String
const schema = {
type: "object",
properties: {
country: { type: "string" },
},
if: { properties: { country: { const: "US" } } },
then: {
required: ["stateCode"],
minProperties: 2,
},
else: {
required: ["countryCode"],
},
errorMessage: {
then: "US addresses need a state code",
else: "Non-US addresses need a country code",
},
};Drilling Into then/else
const schema = {
if: { properties: { country: { const: "US" } } },
then: {
properties: { postalCode: { pattern: "^[0-9]{5}$" } },
},
errorMessage: {
then: {
properties: {
postalCode: "Invalid ZIP code format",
},
},
},
};Defined Inside then (Current Level)
const schema = {
if: { properties: { country: { const: "US" } } },
then: {
properties: { postalCode: { pattern: "^[0-9]{5}$" } },
errorMessage: {
properties: {
postalCode: "Invalid ZIP code format",
},
},
},
};Array prefixItems/items(draft 07)
Index-accessed — indexed or array form, no general string.
// Array form
const schema = {
type: "array",
prefixItems: [{ type: "string" }, { type: "number" }],
errorMessage: {
prefixItems: [
"First item must be a string",
"Second item must be a number",
],
},
};
// Object form
const schemaObj = {
type: "array",
prefixItems: [{ type: "string" }, { type: "number" }],
errorMessage: {
prefixItems: {
0: "First item must be a string",
1: "Second item must be a number",
},
},
};
// Per-keyword within an index
const schemaDetailed = {
type: "array",
prefixItems: [{ type: "string" }, { type: "number" }],
errorMessage: {
prefixItems: {
0: { type: "First item must be a string" },
1: { type: "Second item must be a number" },
},
},
};Single-Schema Array Keywords
additionalItems(draft07), unevaluatedItems, and single-schema items(draft2019) are schema-accepting:
const schema = {
type: "array",
prefixItems: [{ type: "string" }],
items: false,
errorMessage: {
items: "No additional items allowed",
},
};Object Properties
properties and patternProperties are key-accessed. Same structure — the regex is the key for patternProperties.
const schema = {
type: "object",
properties: {
c: { type: "string" },
},
patternProperties: {
"^[0-9]+$": { type: "number" },
},
errorMessage: {
properties: {
c: "c must be a string",
},
patternProperties: {
"^[0-9]+$": { type: "Numeric keys must map to numbers" },
},
},
};additionalProperties / unevaluatedProperties
Schema-accepting — general string or drill-in:
const schema = {
type: "object",
properties: {
name: { type: "string" },
},
additionalProperties: { type: "number" },
errorMessage: {
additionalProperties: "Additional properties must be numbers",
},
};propertyNames
Two forms — string, or keyword-targeted:
// String form
const schema1 = {
type: "object",
propertyNames: { pattern: "^[a-z]+$" },
errorMessage: {
propertyNames: "Property names must be lowercase letters",
},
};
// Keyword-targeted form
const schema2 = {
type: "object",
propertyNames: { pattern: "^[a-z]+$" },
errorMessage: {
propertyNames: {
pattern: "Property names must be lowercase letters",
},
},
};required
General string only:
const schema = {
type: "object",
properties: {
email: { type: "string" },
name: { type: "string" },
},
required: ["email", "name"],
errorMessage: {
required: "Email and name are both required",
},
};dependentRequired
Validation-keyword mapping. It carries a rule and produces errors under its own keyword name, so it has three forms.
Form 1 — whole-keyword general string. One message for every dependentRequired failure, no matter which key or which missing dependency:
const schema = {
type: "object",
properties: {
creditCard: { type: "string" },
cvv: { type: "string" },
billingAddress: { type: "string" },
},
dependentRequired: {
creditCard: ["cvv", "billingAddress"],
email: ["verified"],
},
errorMessage: {
dependentRequired: "A required dependency is missing",
},
};Form 2 — per-key string. One message per triggering property, covering all of that property’s dependencies:
const schema = {
type: "object",
dependentRequired: {
creditCard: ["cvv", "billingAddress"],
email: ["verified"],
},
errorMessage: {
dependentRequired: {
creditCard: "CVV and billing address required for credit card payments",
email: "Verification status required with email",
},
},
};Form 3 — per-key, per-dependency. Drill into a specific missing dependency. A string at the key level acts as the general message for that key; an object targets individual dependencies:
const schema = {
type: "object",
dependentRequired: {
creditCard: ["cvv", "billingAddress"],
email: ["verified"],
},
errorMessage: {
dependentRequired: {
creditCard: {
cvv: "CVV is required for card payments",
billingAddress: "Billing address is required for card payments",
},
email: "Verification status required", // per-key string still fine alongside
},
},
};dependentSchemas
Key-accessed mapping — same rules as properties. Each key maps to a sub-schema, targeted per-key, one level down:
const schema = {
type: "object",
dependentSchemas: {
paymentMethod: {
required: ["cardNumber"],
maxProperties: 2,
},
},
errorMessage: {
dependentSchemas: {
paymentMethod: {
_jetError: "Payment method rules failed",
required: "Card number required when payment method is present",
},
},
},
};Because the one-level-down rule applies, deeper nesting inside a dependentSchemas sub-schema must be defined on that sub-schema directly, or from root:
// ❌ too deep from this level
errorMessage: {
dependentSchemas: {
type: {
properties: { kkk: { type: "won't resolve" } }, // more than one level down
},
},
}dependencies (Legacy)
Split by form:
- Array form (key → required field names) behaves like
dependentRequired - Schema form (key → sub-schema) behaves like
dependentSchemas
const schema = {
type: "object",
dependencies: {
creditCard: ["cvv"], // array form - acts as dependentRequired above
email: { type: "object", required: ["verified"] }, // schema form - acts as dependentSchemas above
},
errorMessage: {
dependencies: {
creditCard: "CVV required with credit card",
email: {
required: "Verification status required",
},
},
},
};not
No errors are collected inside not (its purpose is to fail). Define the message for the not keyword itself:
const schema = {
type: "string",
not: { pattern: "^admin" },
errorMessage: {
not: "Username cannot start with 'admin'",
},
};Centralized from Root
Any of the above can be authored from root by following the full path. Useful when you want all messages in one place:
const schema = {
type: "object",
properties: {
user: {
type: "object",
properties: {
profile: {
type: "object",
properties: {
name: { type: "string", minLength: 2 },
age: { type: "number", minimum: 0 },
},
},
},
},
},
errorMessage: {
properties: {
user: {
_jetError: "User object invalid",
properties: {
profile: {
_jetError: "Profile invalid",
properties: {
name: "Invalid name",
age: "Invalid age",
},
},
},
},
},
},
};Best Practices
- Prefer the current level — define
errorMessageon the schema being validated when you can. It’s the shortest path and the easiest to read. - Use
_jetErrorwhen a level mixes navigation and plain keywords — cover the plain keywords in bulk, target the navigation keywords explicitly. - Know the form for each keyword — index/key-accessed keywords (
properties,anyOf,prefixItems, etc.) never take a general string; schema-accepting keywords (then,additionalProperties, etc.) do. - Respect the one-level-down limit — at non-root levels you can only reach one level down. Go deeper by defining closer to the failure, or from root.
- Use root for centralized messages — root can follow the full path to any depth. Good for keeping all messages in one place, at the cost of verbosity.