Custom error messages in JSON Schemas for Ajv validator
npx @tessl/cli install tessl/npm-ajv-errors@3.0.0ajv-errors is a plugin for the Ajv JSON Schema validator (v8+) that enables custom error messages in JSON schemas. It allows developers to replace Ajv's default validation error messages with user-friendly, contextual messages that can include templated values from the validated data.
npm install ajv-errorsimport ajvErrors from "ajv-errors";
import Ajv from "ajv";For CommonJS:
const ajvErrors = require("ajv-errors");
const Ajv = require("ajv").default;import ajvErrors from "ajv-errors";
import Ajv from "ajv";
// Create Ajv instance with required configuration
const ajv = new Ajv({ allErrors: true });
// Add ajv-errors plugin
ajvErrors(ajv, { keepErrors: false, singleError: false });
// Use errorMessage in schema
const schema = {
type: "object",
required: ["name", "age"],
properties: {
name: { type: "string", minLength: 2 },
age: { type: "number", minimum: 0 }
},
additionalProperties: false,
errorMessage: "Should be a person object with valid name and age"
};
const validate = ajv.compile(schema);
const isValid = validate({ name: "A", age: -1 });
console.log(validate.errors); // Custom error message instead of default onesajv-errors works by:
errorMessage keyword to Ajv instancesMain function to add custom error message support to an Ajv instance.
/**
* Adds errorMessage keyword support to an Ajv instance
* @param ajv - Ajv validator instance (must have allErrors: true)
* @param options - Configuration options for error message behavior
* @returns The same Ajv instance with errorMessage keyword added
* @throws Error if allErrors is false or jsPropertySyntax is true
*/
function ajvErrors(ajv: Ajv, options?: ErrorMessageOptions): Ajv;
interface ErrorMessageOptions {
/** Keep original errors alongside custom messages (default: false) */
keepErrors?: boolean;
/** Merge multiple errors into single error (default: false) */
singleError?: boolean | string;
}The errorMessage keyword accepts various formats for different use cases.
interface ErrorMessageSchema {
/** Custom messages for property-specific errors */
properties?: { [propertyName: string]: string };
/** Custom messages for array item errors by index */
items?: string[];
/** Custom messages for required property errors */
required?: string | { [propertyName: string]: string };
/** Custom messages for dependency errors */
dependencies?: string | { [propertyName: string]: string };
/** Default fallback message for unspecified errors */
_?: string;
/** Custom messages for specific validation keywords */
[keyword: string]: string | { [propertyName: string]: string } | string[] | undefined;
}Replace all validation errors with one custom message.
// String format - replaces all errors
const schema = {
type: "object",
required: ["foo"],
properties: { foo: { type: "integer" } },
additionalProperties: false,
errorMessage: "Should be an object with an integer property foo only"
};Replace errors from specific validation keywords.
// Object format - keyword-specific messages
const schema = {
type: "object",
required: ["foo"],
properties: { foo: { type: "integer" } },
additionalProperties: false,
errorMessage: {
type: "Should be an object",
required: "Should have property foo",
additionalProperties: "Should not have extra properties"
}
};Replace errors for specific properties or array items.
// Property-specific messages
const schema = {
type: "object",
properties: {
name: { type: "string", minLength: 2 },
age: { type: "number", minimum: 0 }
},
errorMessage: {
properties: {
name: "Name must be a string with at least 2 characters",
age: "Age must be a non-negative number"
}
}
};
// Array item messages
const schema = {
type: "array",
items: [
{ type: "string" },
{ type: "number" }
],
errorMessage: {
items: [
"First item must be a string",
"Second item must be a number"
]
}
};Different messages for different required properties.
const schema = {
type: "object",
required: ["name", "email"],
properties: {
name: { type: "string" },
email: { type: "string", format: "email" }
},
errorMessage: {
required: {
name: "Name is required",
email: "Email address is required"
}
}
};Use JSON pointers to include data values in error messages.
const schema = {
type: "object",
properties: {
price: { type: "number", minimum: 0 }
},
errorMessage: {
properties: {
price: "Price must be non-negative, got ${/price}"
}
}
};
// For property names, use relative JSON pointer
const schema = {
type: "object",
additionalProperties: {
not: true,
errorMessage: "Extra property ${0#} is not allowed"
}
};Use _ property for fallback messages when using object format.
const schema = {
type: "object",
required: ["name"],
properties: {
name: { type: "string" }
},
errorMessage: {
type: "Must be an object",
properties: {
name: "Name must be a string"
},
_: "Object validation failed" // Catches any other errors
}
};interface ErrorMessageOptions {
/**
* Keep original errors alongside custom messages
* - false (default): Remove original errors (available in params.errors)
* - true: Keep original errors, mark replaced ones with emUsed: true
*/
keepErrors?: boolean;
/**
* Merge multiple error messages into single error
* - false (default): Create separate error for each message
* - true: Merge with "; " separator
* - string: Use custom separator for merging
*/
singleError?: boolean | string;
}
interface ErrorMessageSchema {
/** Custom messages for property validation errors */
properties?: { [propertyName: string]: string };
/** Custom messages for array item validation errors by index */
items?: string[];
/** Custom messages for required field validation errors */
required?: string | { [propertyName: string]: string };
/** Custom messages for dependency validation errors */
dependencies?: string | { [propertyName: string]: string };
/** Default message for errors not handled by other properties */
_?: string;
/** Custom messages for any validation keyword */
[keyword: string]: string | { [propertyName: string]: string } | string[] | undefined;
}When ajv-errors processes validation errors, it generates new error objects:
// Generated error structure
interface CustomErrorObject {
keyword: "errorMessage";
message: string; // Your custom error message
instancePath: string; // JSON pointer to invalid data
schemaPath: string; // JSON pointer to schema location
params: {
errors: ErrorObject[]; // Original validation errors that were replaced
};
// ... other standard Ajv error properties
}ajv-errors has specific requirements for the Ajv instance:
// Correct Ajv configuration
const ajv = new Ajv({
allErrors: true, // Required
jsPropertySyntax: false // Required (or omit, false is default)
});
// This will throw an error
const badAjv = new Ajv({ allErrors: false });
ajvErrors(badAjv); // Error: "ajv-errors: Ajv option allErrors must be true"import ajvErrors from "ajv-errors";
import Ajv from "ajv";
const ajv = new Ajv({ allErrors: true });
ajvErrors(ajv);
const userSchema = {
type: "object",
required: ["username", "email", "password"],
properties: {
username: {
type: "string",
minLength: 3,
maxLength: 20,
pattern: "^[a-zA-Z0-9_]+$"
},
email: {
type: "string",
format: "email"
},
password: {
type: "string",
minLength: 8
}
},
additionalProperties: false,
errorMessage: {
required: {
username: "Username is required",
email: "Email address is required",
password: "Password is required"
},
properties: {
username: "Username must be 3-20 characters, letters/numbers/underscores only",
email: "Please provide a valid email address",
password: "Password must be at least 8 characters long"
},
additionalProperties: "Only username, email, and password are allowed"
}
};
const validate = ajv.compile(userSchema);const productSchema = {
type: "object",
required: ["id", "name", "price"],
properties: {
id: { type: "string", pattern: "^prod_[0-9]+$" },
name: { type: "string", minLength: 1 },
price: { type: "number", minimum: 0 },
category: { type: "string" }
},
errorMessage: {
required: "Product missing required field: ${/missingProperty}",
properties: {
id: "Product ID '${/id}' must match format 'prod_123'",
name: "Product name cannot be empty",
price: "Price ${/price} must be a non-negative number"
}
}
};const conditionalSchema = {
type: "object",
properties: {
type: { enum: ["personal", "business"] },
taxId: { type: "string" }
},
if: { properties: { type: { const: "business" } } },
then: { required: ["taxId"] },
errorMessage: {
if: "When type is 'business', taxId is required",
_: "Invalid account configuration"
}
};