Painless forms for Vue.js with comprehensive validation, composition API, and component-based approaches.
—
Global configuration and custom validation rule definition for VeeValidate. These functions allow you to customize validation behavior and define reusable validation rules across your application.
Registers a custom validation rule globally that can be used throughout the application.
/**
* Registers a custom validation rule globally
* @param id - Unique rule identifier
* @param validator - Rule implementation function
*/
function defineRule<TValue = unknown, TParams extends any[] = any[]>(
id: string,
validator: ValidationRuleFunction<TValue, TParams> | SimpleValidationRuleFunction<TValue, TParams>
): void;
type ValidationRuleFunction<TValue, TParams extends any[]> = (
value: TValue,
params: TParams,
ctx: FieldValidationMetaInfo
) => MaybePromise<boolean | string>;
type SimpleValidationRuleFunction<TValue, TParams extends any[]> = (
value: TValue,
...params: TParams
) => MaybePromise<boolean | string>;
interface FieldValidationMetaInfo {
field: string; // Field name
name: string; // Field display name
label?: string; // Field label
form: Record<string, unknown>; // Current form values
rule?: { // Rule information
name: string; // Rule name
params?: Record<string, unknown> | unknown[]; // Rule parameters
};
}defineRule Examples:
import { defineRule } from "vee-validate";
// Simple validation rule
defineRule('required', (value: any) => {
if (!value || (Array.isArray(value) && value.length === 0)) {
return 'This field is required';
}
return true;
});
// Rule with parameters
defineRule('min', (value: string | number, [min]: [number]) => {
if (!value) return true; // Don't validate empty values
const length = typeof value === 'string' ? value.length : value;
if (length < min) {
return `This field must be at least ${min} characters`;
}
return true;
});
// Rule with multiple parameters
defineRule('between', (value: number, [min, max]: [number, number]) => {
if (value == null) return true;
if (value < min || value > max) {
return `This field must be between ${min} and ${max}`;
}
return true;
});
// Advanced rule with form context
defineRule('password_confirmation', (value: string, [], ctx) => {
const password = ctx.form.password;
if (value !== password) {
return 'Password confirmation does not match';
}
return true;
});
// Async validation rule
defineRule('unique_email', async (value: string) => {
if (!value) return true;
try {
const response = await fetch(`/api/check-email?email=${value}`);
const { available } = await response.json();
return available || 'This email is already taken';
} catch (error) {
return 'Unable to verify email availability';
}
});
// Rule with custom error messages based on parameters
defineRule('phone', (value: string, [format]: [string] = ['US']) => {
if (!value) return true;
const patterns = {
US: /^\(\d{3}\) \d{3}-\d{4}$/,
UK: /^\+44 \d{4} \d{6}$/,
INTERNATIONAL: /^\+\d{1,3} \d{1,14}$/
};
const pattern = patterns[format as keyof typeof patterns] || patterns.US;
if (!pattern.test(value)) {
return `Please enter a valid ${format} phone number`;
}
return true;
});
// Complex validation with multiple conditions
defineRule('strong_password', (value: string) => {
if (!value) return true;
const errors: string[] = [];
if (value.length < 8) {
errors.push('at least 8 characters');
}
if (!/[A-Z]/.test(value)) {
errors.push('one uppercase letter');
}
if (!/[a-z]/.test(value)) {
errors.push('one lowercase letter');
}
if (!/\d/.test(value)) {
errors.push('one number');
}
if (!/[!@#$%^&*(),.?":{}|<>]/.test(value)) {
errors.push('one special character');
}
if (errors.length > 0) {
return `Password must contain ${errors.join(', ')}`;
}
return true;
});
// Conditional validation rule
defineRule('required_if', (value: any, [targetField, targetValue]: [string, any], ctx) => {
const fieldValue = ctx.form[targetField];
if (fieldValue === targetValue && !value) {
return `This field is required when ${targetField} is ${targetValue}`;
}
return true;
});Sets global validation configuration that affects all forms and fields.
/**
* Sets global validation configuration
* @param config - Configuration object with validation settings
*/
function configure(config: Partial<VeeValidateConfig>): void;
interface VeeValidateConfig {
bails?: boolean; // Stop validation on first error
generateMessage?: ValidationMessageGenerator; // Custom message generator function
validateOnInput?: boolean; // Validate on input events
validateOnChange?: boolean; // Validate on change events
validateOnBlur?: boolean; // Validate on blur events
validateOnModelUpdate?: boolean; // Validate on v-model updates
}
type ValidationMessageGenerator = (ctx: ValidationMessageGeneratorContext) => string;
interface ValidationMessageGeneratorContext {
field: string; // Field name
name: string; // Field display name
label?: string; // Field label
value: unknown; // Field value
form: Record<string, unknown>; // Form values
rule: { // Rule information
name: string; // Rule name
params?: Record<string, unknown> | unknown[]; // Rule parameters
};
}configure Examples:
import { configure } from "vee-validate";
// Basic configuration
configure({
validateOnBlur: true,
validateOnChange: false,
validateOnInput: false,
validateOnModelUpdate: true,
bails: true
});
// Custom message generator
configure({
generateMessage: (ctx) => {
const messages: Record<string, string> = {
required: `${ctx.label || ctx.field} is required`,
email: `${ctx.label || ctx.field} must be a valid email`,
min: `${ctx.label || ctx.field} must be at least ${ctx.rule.params?.[0]} characters`,
max: `${ctx.label || ctx.field} must not exceed ${ctx.rule.params?.[0]} characters`,
confirmed: `${ctx.label || ctx.field} confirmation does not match`
};
return messages[ctx.rule.name] || `${ctx.label || ctx.field} is invalid`;
}
});
// Internationalization support
const messages = {
en: {
required: (field: string) => `${field} is required`,
email: (field: string) => `${field} must be a valid email`,
min: (field: string, params: any[]) => `${field} must be at least ${params[0]} characters`
},
es: {
required: (field: string) => `${field} es requerido`,
email: (field: string) => `${field} debe ser un email válido`,
min: (field: string, params: any[]) => `${field} debe tener al menos ${params[0]} caracteres`
}
};
const currentLocale = ref('en');
configure({
generateMessage: (ctx) => {
const locale = messages[currentLocale.value as keyof typeof messages];
const generator = locale[ctx.rule.name as keyof typeof locale];
if (generator) {
return generator(ctx.label || ctx.field, ctx.rule.params || []);
}
return `${ctx.label || ctx.field} is invalid`;
}
});
// Performance optimization configuration
configure({
// Only validate on blur to reduce validation calls
validateOnInput: false,
validateOnChange: false,
validateOnBlur: true,
// Stop on first error to improve performance
bails: true
});
// Development vs production configuration
const isDevelopment = process.env.NODE_ENV === 'development';
configure({
validateOnInput: isDevelopment, // More responsive validation in development
validateOnBlur: true,
bails: !isDevelopment, // Show all errors in development
generateMessage: (ctx) => {
if (isDevelopment) {
// Detailed error messages in development
return `[${ctx.rule.name}] ${ctx.field}: ${ctx.value} failed validation`;
}
// User-friendly messages in production
return getProductionMessage(ctx);
}
});
const getProductionMessage = (ctx: ValidationMessageGeneratorContext): string => {
// Production-friendly error messages
const field = ctx.label || humanizeFieldName(ctx.field);
switch (ctx.rule.name) {
case 'required':
return `Please enter your ${field.toLowerCase()}`;
case 'email':
return `Please enter a valid ${field.toLowerCase()}`;
case 'min':
return `${field} is too short`;
case 'max':
return `${field} is too long`;
default:
return `Please check your ${field.toLowerCase()}`;
}
};
const humanizeFieldName = (field: string): string => {
return field
.replace(/([A-Z])/g, ' $1') // Add space before capital letters
.replace(/^./, str => str.toUpperCase()) // Capitalize first letter
.replace(/_/g, ' '); // Replace underscores with spaces
};Integrating VeeValidate with existing validation rule libraries.
import { defineRule } from "vee-validate";
// Integration with validator.js
import validator from "validator";
defineRule('email', (value: string) => {
if (!value) return true;
return validator.isEmail(value) || 'Please enter a valid email address';
});
defineRule('url', (value: string) => {
if (!value) return true;
return validator.isURL(value) || 'Please enter a valid URL';
});
defineRule('credit_card', (value: string) => {
if (!value) return true;
return validator.isCreditCard(value) || 'Please enter a valid credit card number';
});
// Custom business rules
defineRule('business_email', (value: string) => {
if (!value) return true;
const personalDomains = [
'gmail.com', 'yahoo.com', 'hotmail.com',
'outlook.com', 'aol.com', 'icloud.com'
];
const domain = value.split('@')[1];
if (personalDomains.includes(domain)) {
return 'Please use a business email address';
}
return validator.isEmail(value) || 'Please enter a valid email address';
});
// File validation rules
defineRule('file_size', (files: FileList | File[], [maxSize]: [number]) => {
if (!files || files.length === 0) return true;
const fileArray = Array.from(files);
const oversizedFiles = fileArray.filter(file => file.size > maxSize);
if (oversizedFiles.length > 0) {
const maxSizeMB = (maxSize / (1024 * 1024)).toFixed(1);
return `File size must not exceed ${maxSizeMB}MB`;
}
return true;
});
defineRule('file_type', (files: FileList | File[], allowedTypes: string[]) => {
if (!files || files.length === 0) return true;
const fileArray = Array.from(files);
const invalidFiles = fileArray.filter(file =>
!allowedTypes.some(type => file.type.startsWith(type))
);
if (invalidFiles.length > 0) {
return `Only ${allowedTypes.join(', ')} files are allowed`;
}
return true;
});
// Date validation rules
defineRule('date_after', (value: string, [afterDate]: [string]) => {
if (!value) return true;
const inputDate = new Date(value);
const compareDate = new Date(afterDate);
if (inputDate <= compareDate) {
return `Date must be after ${compareDate.toLocaleDateString()}`;
}
return true;
});
defineRule('date_before', (value: string, [beforeDate]: [string]) => {
if (!value) return true;
const inputDate = new Date(value);
const compareDate = new Date(beforeDate);
if (inputDate >= compareDate) {
return `Date must be before ${compareDate.toLocaleDateString()}`;
}
return true;
});
// Age validation
defineRule('min_age', (birthDate: string, [minAge]: [number]) => {
if (!birthDate) return true;
const birth = new Date(birthDate);
const today = new Date();
const age = today.getFullYear() - birth.getFullYear();
const monthDiff = today.getMonth() - birth.getMonth();
const actualAge = monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())
? age - 1
: age;
if (actualAge < minAge) {
return `You must be at least ${minAge} years old`;
}
return true;
});Using defined rules in field validation.
import { useField } from "vee-validate";
// Single rule
const { value: email } = useField('email', 'required');
// Multiple rules with pipe syntax
const { value: password } = useField('password', 'required|min:8|strong_password');
// Rules with parameters
const { value: age } = useField('age', 'required|between:18,100');
// Complex rule combinations
const { value: businessEmail } = useField(
'businessEmail',
'required|business_email'
);
// File upload validation
const { value: avatar } = useField(
'avatar',
'required|file_size:2097152|file_type:image/jpeg,image/png'
);Using rules with object syntax for complex parameter passing.
import { useField } from "vee-validate";
// Object rule syntax
const { value: username } = useField('username', {
required: true,
min: 3,
unique_email: true
});
// Mixed rule formats
const { value: phoneNumber } = useField('phoneNumber', [
'required',
{ phone: 'US' },
(value) => value.startsWith('+1') || 'Phone number must include country code'
]);Applying rules based on dynamic conditions.
import { useField, useFormContext } from "vee-validate";
const form = useFormContext();
// Conditional validation based on other field values
const { value: confirmPassword } = useField(
'confirmPassword',
computed(() => {
const password = form.values.password;
return password ? 'required|password_confirmation' : '';
})
);
// Dynamic rule parameters
const { value: discountCode } = useField(
'discountCode',
computed(() => {
const userTier = form.values.userTier;
const rules = ['required'];
if (userTier === 'premium') {
rules.push('min:8');
} else {
rules.push('min:4');
}
return rules.join('|');
})
);Install with Tessl CLI
npx tessl i tessl/npm-vee-validate