JSON Schema Validation Errors: Reading and Fixing ajv Output

Quick answer

💡ajv validation errors are objects with instancePath, schemaPath, keyword, params, and message fields. The instancePath is a JSON Pointer to the failing data field. Read the keyword to understand which constraint failed, then read params to find the specific mismatch. Paste your JSON into ToolDock JSON Validator to spot structural issues before running ajv.

Error symptoms

  • ajv.errors is an array of objects instead of null after validate(data) returns false
  • instancePath is an empty string even though a nested field is clearly wrong
  • keyword is 'additionalProperties' but you cannot tell which property triggered it
  • format keyword errors do not appear even though the value is clearly invalid
  • ajv throws 'strict mode: unknown keyword' when compiling a schema
  • All errors disappear after the first one because ajv stops at the first failure by default

Common causes

  • Passing data that has extra fields not listed in the schema properties without setting additionalProperties: false
  • Omitting a required field from the request body before sending to the API
  • Providing a number where the schema expects a string, or vice versa
  • Using the format keyword without installing and registering the ajv-formats package
  • Compiling a schema that references draft-07 keywords while using ajv v8 which defaults to draft-2020-12
  • Not calling ajv.compile once and caching the returned validate function, which causes recompilation on every request

When it happens

  • Validating incoming API request bodies against an OpenAPI or custom schema
  • Running schema validation on form submissions before writing to a database
  • Generating TypeScript types from JSON Schema and finding the runtime validation does not match the types
  • Migrating from ajv v6 to v8 and encountering new strict mode errors

Examples and fixes

A schema requires an email string and an age number. The submitted data provides age as a string and omits email entirely. This example shows how to read the ajv error array to understand both failures.

Reading a type error and a required error from ajv

❌ Wrong

const Ajv = require('ajv');
const ajv = new Ajv({ allErrors: true });

const userSchema = {
  type: 'object',
  properties: {
    email: { type: 'string', format: 'email' },
    age: { type: 'integer', minimum: 0 }
  },
  required: ['email', 'age']
};

const submittedData = { age: '25' };
const validate = ajv.compile(userSchema);
const isValid = validate(submittedData);
console.log(validate.errors);

✅ Fixed

const Ajv = require('ajv');
const addFormats = require('ajv-formats');
const ajv = new Ajv({ allErrors: true });
addFormats(ajv);

const userSchema = {
  type: 'object',
  properties: {
    email: { type: 'string', format: 'email' },
    age: { type: 'integer', minimum: 0 }
  },
  required: ['email', 'age']
};

const submittedData = { email: '[email protected]', age: 25 };
const validate = ajv.compile(userSchema);
const isValid = validate(submittedData);
console.log(isValid ? 'valid' : validate.errors);

The broken version is missing ajv-formats, so format: 'email' is silently ignored. It also passes a string '25' where the schema expects an integer, and it omits the required email field. The fixed version registers ajv-formats before compiling, provides correctly typed data, and uses allErrors: true so all failures are reported at once rather than stopping at the first one.

A server-side validation handler converts raw ajv error objects into a structured response that the front-end can use to highlight specific form fields.

Mapping ajv errors to user-facing field validation messages

❌ Wrong

function validateUserRegistration(requestBody) {
  const validate = ajv.compile(registrationSchema);
  if (!validate(requestBody)) {
    // Just returning the raw ajv error dump
    return { error: JSON.stringify(validate.errors) };
  }
  return { success: true };
}

✅ Fixed

const validateRegistration = ajv.compile(registrationSchema);

function validateUserRegistration(requestBody) {
  if (!validateRegistration(requestBody)) {
    const fieldErrors = validateRegistration.errors.reduce((acc, err) => {
      const field = err.instancePath.replace(/^\//, '') ||
        err.params.missingProperty || 'form';
      acc[field] = err.message;
      return acc;
    }, {});
    return { valid: false, errors: fieldErrors };
  }
  return { valid: true };
}

The broken version dumps the raw ajv error array as a JSON string, which is unreadable by front-end code trying to highlight specific fields. It also recompiles the schema on every call, which is expensive. The fixed version compiles once, caches the validate function, and maps each error to a field name using instancePath. For required errors, instancePath is empty so it falls back to params.missingProperty, which contains the name of the missing field.

How ajv error objects describe failing data

When ajv validates data against a schema and finds a constraint violation, it creates an error object with five standard fields: instancePath, schemaPath, keyword, params, and message. These fields together give you a precise description of what failed, where in the data it failed, which schema rule was violated, and what the specific mismatch was. Understanding each field is necessary to write useful validation error handlers.

The instancePath field is a JSON Pointer string that locates the failing value within the data document. A JSON Pointer uses forward slashes as separators, so a path of /user/email points to the email property inside the user object. When the instancePath is an empty string, the error applies to the root data object itself. For required keyword errors, instancePath points to the parent object that is missing a property, not to the missing property itself. The missing property name is found in the params field instead.

The keyword field tells you which schema constraint failed. Common keywords include type (the value has the wrong data type), required (a mandatory property is absent), additionalProperties (the data contains a property not listed in the schema), minimum and maximum (a number is out of range), minLength and maxLength (a string is too short or too long), pattern (a string does not match a regular expression), and format (a string does not conform to a named format such as email or date-time). Each keyword has a corresponding params object with additional detail.

The params field varies by keyword. For a type error, params contains a type property with the expected type name. For a required error, params contains missingProperty with the name of the absent field. For an additionalProperties error, params contains additionalProperty with the name of the unexpected field. For a minimum error, params contains limit with the minimum value and exclusive indicating whether the minimum is exclusive. Reading params is essential for building precise error messages because the message field alone often lacks enough specificity to identify the exact problem.

Reading instancePath to find the bad field

The instancePath field is the most important piece of information in an ajv error object when you need to connect a validation failure to a specific input field. It is formatted as a JSON Pointer as defined by RFC 6901, which uses forward slashes to separate path segments. A leading slash means the path is absolute from the root of the validated document. So /address/city refers to the city property within the address object at the top level of the data.

When converting instancePath values to field names for display in a user interface, you typically want to strip the leading slash. In JavaScript, err.instancePath.replace(/^\//, '') produces a dot-notation-like path if you then replace remaining slashes with dots. For arrays, the path includes the numeric index, such as /items/2/price, which means the price field in the third element of the items array.

For required keyword errors, the instancePath is the path to the containing object, not the path to the missing field. This is because the error is about the object failing to satisfy its required constraint, and the missing field does not exist in the data to be pointed to. The actual missing field name is in err.params.missingProperty. A robust error mapper must check the keyword and handle required errors differently from type, pattern, and other field-level errors.

The allErrors option in the ajv constructor is critical for diagnosing complex validation failures. By default, ajv stops after the first error it encounters. This means that if a submitted form has five invalid fields, you will only see one error object. Setting allErrors to true causes ajv to collect every error before returning, which lets you surface all field problems to the user in a single validation pass. This is almost always the right behavior for form validation, though it can be slightly slower for large data documents with many violations.

Fixing required and additionalProperties errors

Required errors are the most common ajv validation failures in API contexts. They occur when a request body omits a field that the schema lists in the required array. The fix is almost always to add the missing field to the data before submitting it, but the error message from ajv does not always make clear which field is absent. The missingProperty value in params.missingProperty gives you the exact field name.

If you are designing a schema and find that required errors appear for fields that should be optional, move those fields out of the required array. Only list a property in required if the application truly cannot function without it. Optional fields that have sensible default values should not be required. Use the default keyword in the schema to document the default, though ajv does not populate defaults by default and requires the useDefaults option to be enabled.

AdditionalProperties errors appear when the data contains a property that is not listed in the schema's properties object and the schema sets additionalProperties to false. This is a common choice for API request schemas to prevent clients from sending fields that the server does not recognize, which could indicate a typo or a protocol version mismatch. The error tells you the exact extra property name in params.additionalProperty. The fix is either to remove that property from the data or to add it to the schema's properties if it is a legitimate field that was simply not documented.

When you are validating data for an API that should accept any extra fields without complaint, either omit the additionalProperties: false constraint from your schema or set additionalProperties to true explicitly. Note that without additionalProperties: false, extra fields are silently accepted and do not appear in any validation errors. If you want to allow extra fields but still know which unexpected fields were received, you will need to compare the data keys against the schema's properties keys manually outside of ajv.

Pattern errors occur when a string value does not match a regular expression defined in the schema. The error message includes the pattern, but not an explanation of what the pattern means in human terms. When exposing pattern errors in user interfaces, replace the raw pattern string with a human-readable description of the expected format. The ajv-errors package lets you override any keyword's error message with a custom string, which is valuable for all user-facing validation messages.

Custom messages versus raw ajv error objects

Raw ajv error messages are technically accurate but often not suitable for direct display to end users. A message like 'must match format email' or 'must have required property email' is clear to a developer but may confuse a user filling out a registration form. The ajv ecosystem provides two approaches to customizing error messages: the ajv-errors package for schema-level message definitions, and application-level error mapping in your validation handler.

The ajv-errors package extends schemas with an errorMessage keyword that accepts either a string for a single custom message or an object mapping keyword names to specific messages. For example, a schema property can define errorMessage: { type: 'Enter a valid number', minimum: 'Value must be at least 0' }. The package rewrites the ajv error array to replace matching errors with the custom messages. This approach keeps the custom message logic close to the schema definition, which is convenient when schemas are shared across server and client.

Application-level error mapping is more flexible because it does not require modifying the schema. Your validation handler receives the raw ajv errors and converts them into a structured response format. This approach is better when the same schema is used for validation in multiple contexts with different error message requirements, such as an API that returns JSON errors and a web server that renders error messages in HTML templates.

One subtlety is that ajv may produce multiple errors for the same field when using combiners such as oneOf or anyOf. When a data value fails all branches of a oneOf, ajv produces errors for each branch plus a top-level oneOf error. Deduplicating these for user display requires filtering to keep only the most specific errors. A common heuristic is to keep errors with the longest instancePath and discard errors whose schemaPath includes '/oneOf/' or '/anyOf/'.

When format validation silently passes without ajv-formats

One of the most confusing behaviors in ajv v8 is that the format keyword does not throw an error by default when the value violates the format constraint. Instead, format validation is silently skipped unless you explicitly install and register the ajv-formats package. This means that an email field containing 'notanemail' will pass validation if ajv-formats is not installed, even though the schema specifies format: 'email'. Developers often discover this only after going to production and finding that invalid email addresses are being stored in the database.

The ajv-formats package provides implementations for the standard JSON Schema formats defined in the specification, including email, uri, date, date-time, time, ipv4, ipv6, uuid, and several others. After installing the package, you must call addFormats(ajv) before compiling any schemas that use the format keyword. Calling addFormats after compilation does not retroactively add format validation to already-compiled validators.

For email format specifically, ajv-formats validates the general structure of an email address but does not verify that the domain exists or that the mailbox accepts messages. Applications that need stricter email validation should combine ajv-formats with a dedicated email validation library or apply a custom pattern to the field.

Another silent failure mode occurs when ajv v8 encounters a schema keyword it does not recognize. In strict mode, which is the default in ajv v8, unknown keywords cause a compilation error with the message 'strict mode: unknown keyword'. This is actually helpful because it catches typos in keyword names. However, when migrating schemas from ajv v6 or from other validators that used non-standard keywords, this strict mode error prevents compilation. You can disable strict mode with { strict: false } in the constructor options, but it is better to remove or update the unrecognized keywords so the schema is valid against the JSON Schema specification.

Schema compilation and caching for performance

Compiling a JSON Schema with ajv is an expensive operation relative to the actual validation step. ajv converts the schema into an optimized JavaScript validation function, which involves parsing the schema, resolving any $ref references, and generating efficient code. If you call ajv.compile inside a request handler, you pay this compilation cost on every request, which can significantly impact throughput for high-volume APIs.

The correct pattern is to compile schemas once during application startup and store the resulting validate functions in module-level variables or a validation registry. Each call to ajv.compile returns a validate function that is synchronous and stateless and can be called concurrently from multiple requests without any issues. The validate function stores the latest error array in validate.errors after each call, which is overwritten on each subsequent call. If you need to preserve error arrays for logging or debugging after an asynchronous operation, copy the errors array before yielding control.

When an application has many schemas, organize them in a validation module that exports named validate functions. This makes it clear which schemas have been compiled and prevents duplicate compilation. If schemas reference other schemas via $ref, add the referenced schemas to ajv using ajv.addSchema before compiling the referencing schema. Using addSchema rather than compile for shared schemas prevents ajv from compiling the shared schema separately each time it is referenced.

For REST APIs, the validation middleware pattern is efficient and reusable. A middleware factory accepts a schema and returns an Express middleware function that runs the pre-compiled validator. If validation fails, the middleware sends a 400 response with the error details. If validation passes, it calls next. This pattern centralizes validation logic and ensures schemas are compiled once at startup. Combining this with TypeScript types generated from the same JSON Schema, using a tool such as json-schema-to-typescript, creates a single source of truth that enforces consistency between runtime validation and compile-time type checking.

Quick fix checklist

  • Install ajv-formats and call addFormats(ajv) before compiling any schema that uses the format keyword
  • Use allErrors: true in the ajv constructor to collect all validation errors, not just the first one
  • Read params.missingProperty for required errors because instancePath points to the parent object
  • Read params.additionalProperty to find the exact unexpected field name for additionalProperties errors
  • Compile schemas once at startup and cache the validate functions; never compile inside a request handler
  • Use ajv.addSchema for shared schemas referenced by $ref before compiling the parent schema
  • Strip the leading slash from instancePath before using it as a field key in error response objects
  • Test format validation explicitly; without ajv-formats, format constraints silently pass

Related guides

Frequently asked questions

What is instancePath in an ajv error object?

instancePath is a JSON Pointer string that identifies the location of the failing value within the validated data. It uses forward slashes as path separators, so /user/email means the email field inside the user object. For required errors, instancePath points to the parent object and the missing field name is in params.missingProperty.

Why does ajv only return one error even when multiple fields are wrong?

By default, ajv stops validation after the first error. To collect all errors, pass allErrors: true when constructing the ajv instance: new Ajv({ allErrors: true }). With this option, ajv runs all validations and populates validate.errors with every failure before returning.

Why does the format keyword not catch invalid email addresses?

The format keyword requires the ajv-formats package to perform actual validation. Without it, ajv ignores format constraints entirely and accepts any value regardless of the format field. Install ajv-formats with npm install ajv-formats, then call addFormats(ajv) before compiling any schema that uses the format keyword. Once registered, format: 'email' and format: 'uri' will perform real string pattern checks.

What does strict mode: unknown keyword mean in ajv v8?

ajv v8 runs in strict mode by default, which throws a compilation error when a schema contains a keyword that ajv does not recognize. This typically happens when migrating schemas from ajv v6 or from other validators. Remove the unrecognized keyword or pass strict: false to the constructor to disable strict mode.

How do I find which property triggered an additionalProperties error?

The error's params object contains an additionalProperty field with the name of the unexpected property. For example, if the data has a field named 'colour' that the schema does not list, the error will have params: { additionalProperty: 'colour' }. Use this to tell the client exactly which field to remove.

How do I add custom error messages to ajv validation?

Install the ajv-errors package and add the errorMessage keyword to your schema properties. You can provide a string for a single message or an object mapping each keyword to a specific message. For example, errorMessage: { type: 'Must be a number', minimum: 'Must be at least 0' } overrides the default messages for those keywords.

Should I compile schemas inside request handlers?

No. Compiling a schema is expensive because ajv generates optimized validation code. Compile schemas once at application startup and store the returned validate functions in module-level variables. The validate function is stateless and safe to call concurrently from many requests without any locking.

How do I validate data that should allow extra fields not in the schema?

Omit additionalProperties: false from your schema, or explicitly set additionalProperties: true. Without additionalProperties: false, ajv accepts any extra fields without errors. If you want to know which extra fields were received, compare the data's keys against the schema's properties keys manually in your application code.

How do I handle oneOf or anyOf errors that produce too many ajv errors?

When a value fails all branches of oneOf, ajv produces errors for every branch plus a top-level oneOf error. Filter the error array to keep errors with the longest instancePath, which are typically the most specific. Discard errors whose schemaPath contains '/oneOf/' or '/anyOf/' at the end, keeping only the leaf-level failures.

All tools run in your browser. Your data never leaves your device. Last updated: 2026-05-05.