Extensible keyword system for creating custom validation logic, format validators, and vocabulary extensions that extend JSON Schema validation capabilities.
Adds custom keyword definitions that extend the validation capabilities with custom logic, code generation, or macro transformations.
/**
* Adds a custom keyword definition
* @param definition - Keyword definition object
* @returns The Ajv instance for chaining
*/
addKeyword(definition: KeywordDefinition): Ajv;
type KeywordDefinition =
| CodeKeywordDefinition
| FuncKeywordDefinition
| MacroKeywordDefinition;
interface BaseKeywordDefinition {
keyword: string;
type?: JSONType | JSONType[];
schemaType?: JSONType | JSONType[];
allowUndefined?: boolean;
$data?: boolean;
implements?: string[];
before?: string;
post?: boolean;
metaSchema?: AnySchemaObject;
validateSchema?: AnyValidateFunction;
dependencies?: string[];
error?: KeywordErrorDefinition;
}
interface CodeKeywordDefinition extends BaseKeywordDefinition {
compile?: (schema: any, parentSchema: AnySchemaObject, it: SchemaCxt) => DataValidateFunction;
validate?: DataValidateFunction;
code?: (cxt: KeywordCxt, ruleType?: string) => void;
macro?: (schema: any, parentSchema: AnySchemaObject, it: SchemaCxt) => AnySchema;
}
interface FuncKeywordDefinition extends BaseKeywordDefinition {
validate: SchemaValidateFunction | DataValidateFunction;
errors?: boolean;
async?: boolean;
}
interface MacroKeywordDefinition extends BaseKeywordDefinition {
macro: (schema: any, parentSchema: AnySchemaObject) => AnySchema;
}Usage Examples:
import Ajv from "ajv";
const ajv = new Ajv();
// Simple validation function keyword
ajv.addKeyword({
keyword: "range",
type: "number",
schemaType: "array",
validate: function validateRange(schema: [number, number], data: number) {
return data >= schema[0] && data <= schema[1];
}
});
// Use the custom keyword
const schema = {
type: "object",
properties: {
age: {
type: "number",
range: [0, 120]
}
}
};
const validate = ajv.compile(schema);
console.log(validate({ age: 25 })); // true
console.log(validate({ age: 150 })); // false
// Macro keyword that transforms to other schemas
ajv.addKeyword({
keyword: "typeof",
macro: function(schema: string) {
return {
type: "string",
pattern: `^${schema}$`
};
}
});
// Code generation keyword for performance
ajv.addKeyword({
keyword: "even",
type: "number",
code: function(cxt) {
const { gen, data } = cxt;
gen.if(`${data} % 2`, () => cxt.fail());
}
});Removes custom keyword definitions from the validator instance.
/**
* Removes a keyword definition
* @param keyword - Keyword name to remove
* @returns The Ajv instance for chaining
*/
removeKeyword(keyword: string): Ajv;Usage Examples:
import Ajv from "ajv";
const ajv = new Ajv();
// Add custom keyword
ajv.addKeyword({
keyword: "custom",
validate: () => true
});
// Remove the keyword
ajv.removeKeyword("custom");
// Keyword is no longer available
// This would now throw an error:
// ajv.compile({ custom: true });Retrieves information about a keyword definition, including built-in and custom keywords.
/**
* Retrieves keyword definition information
* @param keyword - Keyword name
* @returns Keyword definition or false if not found
*/
getKeyword(keyword: string): AddedKeywordDefinition | false;
interface AddedKeywordDefinition extends KeywordDefinition {
type: JSONType[];
schemaType: JSONType[];
$data: boolean;
implements: string[];
}Usage Examples:
import Ajv from "ajv";
const ajv = new Ajv();
// Check built-in keyword
const maxLengthDef = ajv.getKeyword("maxLength");
console.log(maxLengthDef); // Built-in keyword definition
// Check non-existent keyword
const nonExistent = ajv.getKeyword("nonexistent");
console.log(nonExistent); // false
// Add and check custom keyword
ajv.addKeyword({
keyword: "positive",
type: "number",
validate: (schema: boolean, data: number) => schema ? data > 0 : true
});
const positiveDef = ajv.getKeyword("positive");
console.log(positiveDef); // Custom keyword definitionAdds a collection of related keywords as a vocabulary, enabling organized extension of validation capabilities.
/**
* Adds a vocabulary (collection of keywords)
* @param vocabulary - Array of keyword definitions
* @returns The Ajv instance for chaining
*/
addVocabulary(vocabulary: Vocabulary): Ajv;
type Vocabulary = KeywordDefinition[];Usage Examples:
import Ajv from "ajv";
const ajv = new Ajv();
// Math vocabulary with related keywords
const mathVocabulary: Vocabulary = [
{
keyword: "divisibleBy",
type: "number",
validate: function(schema: number, data: number) {
return data % schema === 0;
}
},
{
keyword: "prime",
type: "number",
validate: function(schema: boolean, data: number) {
if (!schema) return true;
if (data < 2) return false;
for (let i = 2; i * i <= data; i++) {
if (data % i === 0) return false;
}
return true;
}
},
{
keyword: "fibonacci",
type: "number",
validate: function(schema: boolean, data: number) {
if (!schema) return true;
let a = 0, b = 1;
while (b < data) {
[a, b] = [b, a + b];
}
return b === data || data === 0;
}
}
];
ajv.addVocabulary(mathVocabulary);
// Use vocabulary keywords
const schema = {
type: "object",
properties: {
score: {
type: "number",
divisibleBy: 5,
prime: false
},
sequence: {
type: "number",
fibonacci: true
}
}
};Adds custom format validators for string validation with support for both synchronous and asynchronous validation.
/**
* Adds a custom format validator
* @param name - Format name
* @param format - Format definition (string, RegExp, or function)
* @returns The Ajv instance for chaining
*/
addFormat(name: string, format: Format): Ajv;
type Format = string | RegExp | FormatDefinition<string> | FormatDefinition<number>;
interface FormatDefinition<T> {
validate: FormatValidator<T>;
compare?: FormatCompare<T>;
async?: boolean;
}
type FormatValidator<T> = (data: T) => boolean;
type FormatCompare<T> = (data1: T, data2: T) => number | undefined;Usage Examples:
import Ajv from "ajv";
const ajv = new Ajv();
// RegExp format
ajv.addFormat("username", /^[a-zA-Z0-9_]{3,16}$/);
// Function format with comparison
ajv.addFormat("credit-card", {
validate: function(data: string) {
// Luhn algorithm validation
const digits = data.replace(/\D/g, '');
if (digits.length < 13 || digits.length > 19) return false;
let sum = 0;
let isEven = false;
for (let i = digits.length - 1; i >= 0; i--) {
let digit = parseInt(digits[i]);
if (isEven) {
digit *= 2;
if (digit > 9) digit -= 9;
}
sum += digit;
isEven = !isEven;
}
return sum % 10 === 0;
}
});
// Async format validation
ajv.addFormat("unique-email", {
async: true,
validate: async function(data: string) {
// Simulate database check
const response = await fetch(`/api/check-email/${data}`);
const result = await response.json();
return result.isUnique;
}
});
// Use custom formats
const userSchema = {
type: "object",
properties: {
username: {
type: "string",
format: "username"
},
email: {
type: "string",
format: "unique-email"
},
creditCard: {
type: "string",
format: "credit-card"
}
}
};Custom keywords can provide detailed error information:
ajv.addKeyword({
keyword: "isbn",
error: {
message: "must be a valid ISBN",
params: (cxt) => ({ format: "isbn" })
},
validate: function validateISBN(schema: boolean, data: string) {
if (!schema) return true;
const isbn = data.replace(/[^0-9X]/gi, '').toUpperCase();
if (isbn.length !== 10 && isbn.length !== 13) return false;
// ISBN validation logic...
return true; // simplified
}
});Keywords have access to compilation context for advanced features:
ajv.addKeyword({
keyword: "excludeFields",
code: function(cxt) {
const { schema, gen, data } = cxt;
const excluded = schema; // Array of field names to exclude
gen.if(`${data} && typeof ${data} === "object"`, () => {
excluded.forEach((field: string) => {
gen.if(`${data}.hasOwnProperty(${JSON.stringify(field)})`, () => {
cxt.fail({ message: `field ${field} is not allowed` });
});
});
});
}
});Ajv includes several built-in vocabularies:
Each vocabulary can be selectively enabled or disabled based on your validation needs.