PolyVal provides a flexible error message system with built-in internationalization and multiple levels of customization. Error messages follow a clear priority system to ensure you have complete control over how validation errors are presented.
Default messages
PolyVal includes default error messages in English and Turkish:
// From src/messages/en.ts
export const enMessages = {
required: "This field is required",
invalid_type: "Invalid type",
string: {
min: (min: number) => `Must be at least ${min} characters long`,
max: (max: number) => `Must not exceed ${max} characters`,
length: (len: number) => `Must be exactly ${len} characters long`,
email: "Invalid email address",
url: "Invalid URL",
uuid: "Invalid UUID",
cuid: "Invalid CUID",
datetime: "Invalid datetime format",
ip: "Invalid IP address",
regex: "Invalid format",
startsWith: (value: string) => `Must start with "${value}"`,
endsWith: (value: string) => `Must end with "${value}"`,
numeric: "Must contain only numeric characters"
},
number: {
min: (min: number) => `Must be at least ${min}`,
max: (max: number) => `Must not exceed ${max}`
},
date: {
min: (date: Date) => `Must be after ${date.toLocaleDateString()}`,
max: (date: Date) => `Must be before ${date.toLocaleDateString()}`
},
boolean: {
true: "Must be checked",
false: "Must be unchecked"
},
equals: (field: string) => `Must match the ${field} field`,
notEquals: (field: string) => `Must not match the ${field} field`
};
Selecting a language
Choose a language by passing the lang option:
import { validate } from 'polyval';
const schema = {
username: {
type: 'string',
required: true,
min: 3
}
};
const data = { username: 'jo' };
// English messages
const errorsEN = validate(schema, data, { lang: 'en' });
// ["Username: Must be at least 3 characters long"]
// Turkish messages
const errorsTR = validate(schema, data, { lang: 'tr' });
// ["Username: En az 3 karakter uzunluğunda olmalıdır"]
Supported languages: 'en' (English) and 'tr' (Turkish)
Custom messages
Customize error messages at multiple levels using the customMessages option:
const errors = validate(schema, data, {
lang: 'en',
customMessages: {
// Your custom messages here
}
});
Message priority system
PolyVal uses a clear priority order when selecting error messages. Higher priority messages override lower priority ones:
- Field-based custom validator messages -
customMessages.fields[fieldName][customMessageKey]
- Field-based rule messages -
customMessages.fields[fieldName].min, .email, etc.
- Global custom validator messages -
customMessages.custom[messageKey]
- Type-based rule messages -
customMessages.string.email, customMessages.number.min, etc.
- General error messages -
customMessages.required, customMessages.invalid_type
- Default language messages - Built-in messages for the selected language
The priority system ensures field-specific messages always take precedence over general messages. This allows you to provide specific guidance for important fields while maintaining default messages for others.
Customization levels
Level 1: General error messages
Override general error messages for all fields:
const errors = validate(schema, data, {
lang: 'en',
customMessages: {
required: 'This field cannot be empty',
invalid_type: 'Invalid value type'
}
});
Level 2: Type-based messages
Customize messages for specific validation rules across all fields of a type:
const errors = validate(schema, data, {
lang: 'en',
customMessages: {
string: {
min: (min) => `Please enter at least ${min} characters`,
max: (max) => `Please keep it under ${max} characters`,
email: 'Please enter a valid email address',
url: 'Please enter a valid URL'
},
number: {
min: (min) => `Value must be at least ${min}`,
max: (max) => `Value cannot exceed ${max}`
},
date: {
min: (date) => `Date must be after ${date.toLocaleDateString()}`,
max: (date) => `Date must be before ${date.toLocaleDateString()}`
}
}
});
Implementation example from source:
// From src/index.ts:117-134
if (typeof fieldConfig.min === 'number' && value.length < fieldConfig.min) {
let errorMessage: string;
// Priority order
if (customMessages?.fields?.[fieldName]?.min) {
// Field-based custom message
const messageFunc = customMessages.fields[fieldName].min as (min: number) => string;
errorMessage = messageFunc(fieldConfig.min);
} else if (customMessages?.string?.min) {
// Type-based custom message
errorMessage = customMessages.string.min(fieldConfig.min);
} else {
// Default message
errorMessage = messages.string.min(fieldConfig.min);
}
errors.push(`${formattedFieldName}: ${errorMessage}`);
}
Level 3: Field comparison messages
Customize messages for field comparison rules:
const errors = validate(schema, data, {
lang: 'en',
customMessages: {
equals: (field) => `This field must match the ${field} field`,
notEquals: (field) => `This field must not match the ${field} field`
}
});
Level 4: Field-based messages (highest priority)
Provide custom messages for specific fields:
const errors = validate(schema, data, {
lang: 'en',
customMessages: {
fields: {
username: {
min: (min) => `Username must be at least ${min} characters`,
max: (max) => `Username cannot exceed ${max} characters`,
required: 'Please choose a username'
},
email: {
required: 'We need your email address',
email: 'Please check your email format'
},
password: {
min: (min) => `For security, use at least ${min} characters`,
required: 'Password is required'
}
}
}
});
Level 5: Custom validator messages
Provide messages for custom validators at global or field level:
const schema = {
username: {
type: 'string',
customValidators: [
{
validator: (value) => {
return value.toLowerCase() !== 'admin'
? undefined
: 'Default error message';
},
messageKey: 'noAdminUsername'
}
]
},
email: {
type: 'string',
customValidators: [
{
validator: (value) => {
return !value.includes('tempmail.com')
? undefined
: 'Default error';
},
messageKey: 'noTempEmail'
}
]
}
};
const errors = validate(schema, data, {
lang: 'en',
customMessages: {
// Global custom validator messages
custom: {
noAdminUsername: 'Admin username is reserved',
noTempEmail: 'Temporary email addresses are not allowed'
},
// Field-specific custom validator messages (higher priority)
fields: {
username: {
noAdminUsername: 'The username "admin" is reserved for administrators'
}
}
}
});
Implementation from source:
// From src/index.ts:342-361
if (validator.messageKey) {
// Priority order
if (customMessages?.fields?.[fieldName]?.[validator.messageKey]) {
// Field-based custom message
const messageProvider = customMessages.fields[fieldName][validator.messageKey];
if (typeof messageProvider === 'function') {
errorMessage = (messageProvider as (value: any, data: Record<string, any>) => string)(value, data);
} else {
errorMessage = messageProvider as string;
}
} else if (customMessages?.custom?.[validator.messageKey]) {
// Global custom message
const messageProvider = customMessages.custom[validator.messageKey];
if (typeof messageProvider === 'function') {
errorMessage = (messageProvider as (value: any, data: Record<string, any>) => string)(value, data);
} else {
errorMessage = messageProvider as string;
}
}
}
Custom validator messages can be strings or functions. Use functions when you need to include the field value or other data in the error message.
Complete example
Here’s a comprehensive example demonstrating all customization levels:
import { validate } from 'polyval';
const userSchema = {
username: {
type: 'string',
required: true,
min: 3,
max: 20,
customValidators: [
{
validator: (value) => {
return value.toLowerCase() !== 'admin'
? undefined
: 'Error';
},
messageKey: 'noAdminUsername'
}
]
},
email: {
type: 'string',
required: true,
email: true
},
age: {
type: 'number',
min: 18
},
password: {
type: 'string',
required: true,
min: 8
},
confirmPassword: {
type: 'string',
required: true,
equals: 'password'
}
};
const userData = {
username: 'admin',
email: 'invalid-email',
age: 16,
password: 'short',
confirmPassword: 'different'
};
const errors = validate(userSchema, userData, {
lang: 'en',
customMessages: {
// Level 1: General messages
required: 'This field cannot be empty',
// Level 2: Type-based messages
string: {
min: (min) => `Please enter at least ${min} characters`,
email: 'Please provide a valid email address'
},
number: {
min: (min) => `Must be ${min} or greater`
},
// Level 3: Field comparison messages
equals: (field) => `Must match ${field}`,
// Level 4: Global custom validator messages
custom: {
noAdminUsername: 'Admin username is reserved'
},
// Level 5: Field-specific messages (highest priority)
fields: {
username: {
required: 'Please choose a username',
min: (min) => `Username needs at least ${min} characters`,
noAdminUsername: 'Sorry, "admin" is a reserved username'
},
password: {
min: (min) => `For your security, use at least ${min} characters`
},
confirmPassword: {
equals: 'Passwords must match exactly'
}
}
}
});
console.log(errors);
// [
// 'Username: Sorry, "admin" is a reserved username',
// 'Email: Please provide a valid email address',
// 'Age: Must be 18 or greater',
// 'Password: For your security, use at least 8 characters',
// 'ConfirmPassword: Passwords must match exactly'
// ]
All validation errors follow the format:
[FormattedFieldName]: [Error Message]
Field names are automatically formatted with the first letter capitalized:
const schema = {
username: { type: 'string', required: true },
firstName: { type: 'string', required: true }
};
const errors = validate(schema, {}, { lang: 'en' });
// [
// "Username: This field is required",
// "FirstName: This field is required"
// ]
Implementation from source:
// From src/index.ts:31-32
// Format field name to start with uppercase letter
const formattedFieldName = fieldName.charAt(0).toUpperCase() + fieldName.slice(1);
The validate() function returns an array of error strings. An empty array means validation passed successfully.