Validation Adapters
Integrate popular validation libraries like Zod, Yup, and Joi with Rilay forms.
The @rilaykit/validation-adapters
package provides seamless integration between popular schema validation libraries and Rilay's form system, allowing you to use your existing validation schemas.
Installation
# Install the adapters package
npm install @rilaykit/validation-adapters
# Install your preferred validation library
npm install zod
# Or for Yup
npm install yup
# Or for Joi
npm install joi
# Install the adapters package
pnpm add @rilaykit/validation-adapters
# Install your preferred validation library
pnpm add zod
# Or for Yup
pnpm add yup
# Or for Joi
pnpm add joi
# Install the adapters package
yarn add @rilaykit/validation-adapters
# Install your preferred validation library
yarn add zod
# Or for Yup
yarn add yup
# Or for Joi
yarn add joi
Supported Libraries
Library | Status | Import |
---|---|---|
Zod | ✅ Available | @rilaykit/validation-adapters/zod |
Yup | ✅ Available | @rilaykit/validation-adapters/yup |
Joi | ✅ Available | @rilaykit/validation-adapters/joi |
Zod Integration
Basic Field Validation
Use Zod schemas directly in your Rilay forms:
import { z } from 'zod';
import { createZodValidator } from '@rilaykit/validation-adapters/zod';
// Create Zod validators
const emailValidator = createZodValidator(
z.string().email('Please enter a valid email address')
);
const ageValidator = createZodValidator(
z.number().min(18, 'You must be at least 18 years old')
);
// Use in form configuration
const form = factory
.form('user-registration')
.add({
id: 'email',
type: 'email',
props: { label: 'Email Address' },
validation: {
validators: [emailValidator]
}
})
.add({
id: 'age',
type: 'number',
props: { label: 'Age' },
validation: {
validators: [ageValidator]
}
});
Component-Level Validation
Apply Zod validation to all instances of a component:
const factory = ril
.create()
.addComponent('email', {
renderer: EmailInput,
validation: {
validators: [
createZodValidator(z.string().email('Invalid email format'))
]
}
})
.addComponent('password', {
renderer: PasswordInput,
validation: {
validators: [
createZodValidator(
z.string()
.min(8, 'Password must be at least 8 characters')
.regex(/[A-Z]/, 'Must contain at least one uppercase letter')
.regex(/[0-9]/, 'Must contain at least one number')
)
]
}
});
Form-Level Validation
Validate entire forms with complex cross-field validation:
import { createZodFormValidator } from '@rilaykit/validation-adapters/zod';
// Define complete form schema
const registrationSchema = z.object({
email: z.string().email('Invalid email format'),
password: z.string().min(8, 'Password must be at least 8 characters'),
confirmPassword: z.string(),
terms: z.boolean().refine(val => val === true, 'You must accept the terms')
}).refine(data => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ["confirmPassword"]
});
// Create form validator
const formValidator = createZodFormValidator(registrationSchema);
// Apply to entire form
const form = factory
.form('registration')
.add({ id: 'email', type: 'email' })
.add({ id: 'password', type: 'password' })
.add({ id: 'confirmPassword', type: 'password' })
.add({ id: 'terms', type: 'checkbox' })
.setValidation({
validators: [formValidator]
});
Advanced Features
Async Validation
Integrate async validation like API calls:
// Schema with async refinement
const usernameSchema = z.string()
.min(3, 'Username must be at least 3 characters')
.refine(async (username) => {
const response = await fetch(`/api/check-username/${username}`);
const { available } = await response.json();
return available;
}, 'Username is already taken');
// Create async validator
const asyncValidator = createZodValidator(usernameSchema, {
parseMode: 'async'
});
// Use with debouncing for better UX
const form = factory
.form()
.add({
id: 'username',
type: 'text',
validation: {
validators: [asyncValidator],
validateOnBlur: true,
debounceMs: 500
}
});
Custom Error Transformation
Transform Zod errors to be more user-friendly:
import { zodErrorTransforms } from '@rilaykit/validation-adapters/zod';
const validator = createZodValidator(
z.string().min(3).max(50),
{
// Use built-in transformation
errorTransform: zodErrorTransforms.userFriendly,
// Custom transformation
errorTransform: (issues) => {
const issue = issues[0];
switch (issue.code) {
case 'too_small':
return 'This field is too short';
case 'too_big':
return 'This field is too long';
default:
return issue.message;
}
}
}
);
Path Formatting
Customize how nested field paths are displayed:
import { zodPathFormatters } from '@rilaykit/validation-adapters/zod';
const validator = createZodValidator(
z.object({
user: z.object({
profile: z.object({
name: z.string().min(1)
})
})
}),
{
pathFormatter: zodPathFormatters.humanReadable // "User Profile Name"
// or zodPathFormatters.bracketNotation // "user[profile][name]"
// or zodPathFormatters.dotNotation // "user.profile.name" (default)
}
);
Validation Presets
Use predefined configurations for common scenarios:
import { createZodValidatorWithPreset } from '@rilaykit/validation-adapters/zod';
const validator = createZodValidatorWithPreset(
z.string().email(),
'userFriendly' // Transforms errors to be more readable
);
// Equivalent to:
// createZodValidator(schema, {
// parseMode: 'sync',
// errorTransform: zodErrorTransforms.concatenated,
// pathFormatter: zodPathFormatters.humanReadable,
// abortEarly: false
// })
const validator = createZodValidatorWithPreset(
z.string().email(),
'fast' // Aborts on first error for better performance
);
// Equivalent to:
// createZodValidator(schema, {
// parseMode: 'sync',
// errorTransform: zodErrorTransforms.firstOnly,
// abortEarly: true
// })
const validator = createZodValidatorWithPreset(
z.string().email(),
'strict' // Uses original Zod errors without transformation
);
// Equivalent to:
// createZodValidator(schema, {
// parseMode: 'sync',
// abortEarly: false
// })
const validator = createZodValidatorWithPreset(
z.string().email(),
'async' // Optimized for async validation
);
// Equivalent to:
// createZodValidator(schema, {
// parseMode: 'async',
// errorTransform: zodErrorTransforms.userFriendly,
// pathFormatter: zodPathFormatters.dotNotation,
// abortEarly: false
// })
Real-World Example
Here's a complete example of a complex registration form using Zod validation:
import { z } from 'zod';
import {
createZodValidator,
createZodFormValidator,
zodErrorTransforms
} from '@rilaykit/validation-adapters/zod';
import { ril, when, required } from '@rilaykit/core';
// Define schemas
const personalSchema = z.object({
firstName: z.string().min(2, 'First name must be at least 2 characters'),
lastName: z.string().min(2, 'Last name must be at least 2 characters'),
email: z.string().email('Please enter a valid email address'),
phone: z.string().regex(/^\d{3}-\d{3}-\d{4}$/, 'Phone must be XXX-XXX-XXXX')
});
const businessSchema = z.object({
userType: z.enum(['individual', 'business']),
companyName: z.string().optional(),
taxId: z.string().optional()
}).refine(data => {
if (data.userType === 'business') {
return data.companyName && data.companyName.length > 0;
}
return true;
}, {
message: 'Company name is required for business accounts',
path: ['companyName']
});
// Create validators
const emailValidator = createZodValidator(
personalSchema.shape.email,
{ errorTransform: zodErrorTransforms.userFriendly }
);
const phoneValidator = createZodValidator(
personalSchema.shape.phone,
{ abortEarly: true }
);
const formValidator = createZodFormValidator(
personalSchema.merge(businessSchema)
);
// Configure factory
const factory = ril
.create()
.addComponent('text', { renderer: TextInput })
.addComponent('email', { renderer: EmailInput })
.addComponent('select', { renderer: SelectInput });
// Build form
const registrationForm = factory
.form('registration')
.add({
id: 'userType',
type: 'select',
props: {
label: 'Account Type',
options: [
{ value: 'individual', label: 'Individual' },
{ value: 'business', label: 'Business' }
]
}
})
.add(
{
id: 'firstName',
type: 'text',
props: { label: 'First Name' },
validation: {
validators: [createZodValidator(personalSchema.shape.firstName)]
}
},
{
id: 'lastName',
type: 'text',
props: { label: 'Last Name' },
validation: {
validators: [createZodValidator(personalSchema.shape.lastName)]
}
}
)
.add({
id: 'email',
type: 'email',
props: { label: 'Email Address' },
validation: {
validators: [
emailValidator,
// Mix Zod with built-in validators
required('Email is required')
],
validateOnBlur: true
}
})
.add({
id: 'phone',
type: 'text',
props: { label: 'Phone Number', placeholder: '123-456-7890' },
validation: {
validators: [phoneValidator],
validateOnChange: true,
debounceMs: 300
}
})
.add({
id: 'companyName',
type: 'text',
props: { label: 'Company Name' },
conditions: {
visible: when('userType').equals('business'),
required: when('userType').equals('business')
}
})
.add({
id: 'taxId',
type: 'text',
props: { label: 'Tax ID' },
conditions: {
visible: when('userType').equals('business'),
required: when('userType').equals('business')
}
})
.setValidation({
validators: [formValidator]
});
Integration with Workflows
Zod validators work seamlessly with Rilay workflows:
const onboardingWorkflow = factory
.flow('user-onboarding')
.addStep({
id: 'personal-info',
title: 'Personal Information',
formConfig: factory.form()
.add({
id: 'email',
type: 'email',
validation: {
validators: [
createZodValidator(z.string().email()),
// Mix with conditions
required('Email is required')
]
}
})
})
.addStep({
id: 'verification',
title: 'Email Verification',
conditions: {
visible: when('personal-info.email').isNotEmpty()
},
formConfig: factory.form()
.add({
id: 'verificationCode',
type: 'text',
validation: {
validators: [
createZodValidator(
z.string().length(6, 'Verification code must be 6 digits')
)
]
}
})
});
Yup Integration
Basic Field Validation
Use Yup schemas in your Rilay forms:
import * as yup from 'yup';
import { createYupValidator } from '@rilaykit/validation-adapters/yup';
// Create Yup validators
const emailValidator = createYupValidator(
yup.string()
.email('Please enter a valid email address')
.required('Email is required')
);
const ageValidator = createYupValidator(
yup.number()
.min(18, 'You must be at least 18 years old')
.required('Age is required')
);
// Use in form configuration
const form = factory
.form('user-registration')
.add({
id: 'email',
type: 'email',
props: { label: 'Email Address' },
validation: {
validators: [emailValidator]
}
})
.add({
id: 'age',
type: 'number',
props: { label: 'Age' },
validation: {
validators: [ageValidator]
}
});
Form-Level Validation
Validate entire forms with cross-field validation:
import { createYupFormValidator } from '@rilaykit/validation-adapters/yup';
// Define complete form schema
const registrationSchema = yup.object({
email: yup.string().email('Invalid email format').required('Email is required'),
password: yup.string().min(8, 'Password must be at least 8 characters').required(),
confirmPassword: yup.string()
.required('Confirm password is required')
.oneOf([yup.ref('password')], 'Passwords must match')
});
// Create form validator
const formValidator = createYupFormValidator(registrationSchema);
// Apply to entire form
const form = factory
.form('registration')
.add({ id: 'email', type: 'email' })
.add({ id: 'password', type: 'password' })
.add({ id: 'confirmPassword', type: 'password' })
.setValidation({
validators: [formValidator]
});
Joi Integration
Basic Field Validation
Use Joi schemas in your Rilay forms:
import Joi from 'joi';
import { createJoiValidator } from '@rilaykit/validation-adapters/joi';
// Create Joi validators
const emailValidator = createJoiValidator(
Joi.string()
.email({ tlds: { allow: false } })
.required()
.messages({
'string.email': 'Please enter a valid email address',
'any.required': 'Email is required'
})
);
const ageValidator = createJoiValidator(
Joi.number()
.min(18)
.required()
.messages({
'number.min': 'You must be at least 18 years old',
'any.required': 'Age is required'
})
);
// Use in form configuration
const form = factory
.form('user-registration')
.add({
id: 'email',
type: 'email',
props: { label: 'Email Address' },
validation: {
validators: [emailValidator]
}
})
.add({
id: 'age',
type: 'number',
props: { label: 'Age' },
validation: {
validators: [ageValidator]
}
});
Form-Level Validation
Validate entire forms with complex cross-field validation:
import { createJoiFormValidator } from '@rilaykit/validation-adapters/joi';
// Define complete form schema
const registrationSchema = Joi.object({
email: Joi.string().email().required().messages({
'string.email': 'Invalid email format',
'any.required': 'Email is required'
}),
password: Joi.string().min(8).required().messages({
'string.min': 'Password must be at least 8 characters',
'any.required': 'Password is required'
}),
confirmPassword: Joi.string().required().messages({
'any.required': 'Confirm password is required'
})
}).custom((value, helpers) => {
if (value.password !== value.confirmPassword) {
return helpers.error('any.custom', {
path: ['confirmPassword'],
message: 'Passwords must match'
});
}
return value;
});
// Create form validator
const formValidator = createJoiFormValidator(registrationSchema);
// Apply to entire form
const form = factory
.form('registration')
.add({ id: 'email', type: 'email' })
.add({ id: 'password', type: 'password' })
.add({ id: 'confirmPassword', type: 'password' })
.setValidation({
validators: [formValidator]
});
Advanced Features
Custom Error Transformation
import { zodErrorTransforms } from '@rilaykit/validation-adapters/zod';
const validator = createZodValidator(
z.string().min(3).max(50),
{
// Use built-in transformation
errorTransform: zodErrorTransforms.userFriendly,
// Custom transformation
errorTransform: (issues) => {
const issue = issues[0];
switch (issue.code) {
case 'too_small':
return 'This field is too short';
case 'too_big':
return 'This field is too long';
default:
return issue.message;
}
}
}
);
import { yupErrorTransforms } from '@rilaykit/validation-adapters/yup';
const validator = createYupValidator(
yup.string().min(3).max(50),
{
// Use built-in transformation
errorTransform: yupErrorTransforms.userFriendly,
// Custom transformation
errorTransform: (errors) => {
const error = errors[0];
switch (error.type) {
case 'min':
return 'This field is too short';
case 'max':
return 'This field is too long';
default:
return error.message;
}
}
}
);
import { joiErrorTransforms } from '@rilaykit/validation-adapters/joi';
const validator = createJoiValidator(
Joi.string().min(3).max(50),
{
// Use built-in transformation
errorTransform: joiErrorTransforms.userFriendly,
// Custom transformation
errorTransform: (details) => {
const detail = details[0];
switch (detail.type) {
case 'string.min':
return 'This field is too short';
case 'string.max':
return 'This field is too long';
default:
return detail.message;
}
}
}
);
Validation Presets
Use predefined configurations for common scenarios:
import { createZodValidatorWithPreset } from '@rilaykit/validation-adapters/zod';
// User-friendly preset
const validator = createZodValidatorWithPreset(
z.string().email(),
'userFriendly'
);
// Fast preset (aborts on first error)
const fastValidator = createZodValidatorWithPreset(
z.string().email(),
'fast'
);
// Async preset
const asyncValidator = createZodValidatorWithPreset(
z.string().email(),
'async'
);
import { createYupValidatorWithPreset } from '@rilaykit/validation-adapters/yup';
// User-friendly preset
const validator = createYupValidatorWithPreset(
yup.string().email(),
'userFriendly'
);
// Fast preset (aborts on first error)
const fastValidator = createYupValidatorWithPreset(
yup.string().email(),
'fast'
);
// Lenient preset (strips unknown keys)
const lenientValidator = createYupValidatorWithPreset(
yup.string().email(),
'lenient'
);
import { createJoiValidatorWithPreset } from '@rilaykit/validation-adapters/joi';
// User-friendly preset
const validator = createJoiValidatorWithPreset(
Joi.string().email(),
'userFriendly'
);
// Fast preset (aborts on first error)
const fastValidator = createJoiValidatorWithPreset(
Joi.string().email(),
'fast'
);
// Lenient preset (allows unknown keys)
const lenientValidator = createJoiValidatorWithPreset(
Joi.string().email(),
'lenient'
);
Path Formatting
Customize how nested field paths are displayed:
import { zodPathFormatters } from '@rilaykit/validation-adapters/zod';
const validator = createZodValidator(
z.object({
user: z.object({
profile: z.object({
name: z.string().min(1)
})
})
}),
{
pathFormatter: zodPathFormatters.humanReadable // "User Profile Name"
// or zodPathFormatters.bracketNotation // "user[profile][name]"
// or zodPathFormatters.dotNotation // "user.profile.name" (default)
}
);
import { yupPathFormatters } from '@rilaykit/validation-adapters/yup';
const validator = createYupValidator(
yup.object({
user: yup.object({
profile: yup.object({
name: yup.string().required()
})
})
}),
{
pathFormatter: yupPathFormatters.humanReadable // "User Profile Name"
// or yupPathFormatters.bracketNotation // "user[profile][name]"
// or yupPathFormatters.dotNotation // "user.profile.name" (default)
}
);
import { joiPathFormatters } from '@rilaykit/validation-adapters/joi';
const validator = createJoiValidator(
Joi.object({
user: Joi.object({
profile: Joi.object({
name: Joi.string().required()
})
})
}),
{
pathFormatter: joiPathFormatters.humanReadable // "User Profile Name"
// or joiPathFormatters.bracketNotation // "user[profile][name]"
// or joiPathFormatters.dotNotation // "user.profile.name" (default)
}
);
Convenience Functions
Each adapter provides convenience functions for common use cases:
// Sync and async validators
const syncValidator = createZodSyncValidator(schema);
const asyncValidator = createZodAsyncValidator(schema);
// Form validators
const formValidator = createZodFormValidator(schema);
const strictFormValidator = createZodSyncFormValidator(schema);
const asyncFormValidator = createZodAsyncFormValidator(schema);
// Strict and lenient validators
const strictValidator = createYupStrictValidator(schema);
const lenientValidator = createYupLenientValidator(schema);
// Form validators
const formValidator = createYupFormValidator(schema);
const strictFormValidator = createYupStrictFormValidator(schema);
const lenientFormValidator = createYupLenientFormValidator(schema);
// Strict and lenient validators
const strictValidator = createJoiStrictValidator(schema);
const lenientValidator = createJoiLenientValidator(schema);
// Form validators
const formValidator = createJoiFormValidator(schema);
const strictFormValidator = createJoiStrictFormValidator(schema);
const lenientFormValidator = createJoiLenientFormValidator(schema);
API Reference
Field Validators
// Primary functions
createZodValidator<T>(schema: ZodSchema<T>, options?: ZodValidatorOptions): FieldValidator<T>
createZodSyncValidator<T>(schema: ZodSchema<T>, options?: Omit<ZodValidatorOptions, 'parseMode'>): FieldValidator<T>
createZodAsyncValidator<T>(schema: ZodSchema<T>, options?: Omit<ZodValidatorOptions, 'parseMode'>): FieldValidator<T>
// Preset helpers
createZodValidatorWithPreset<T>(schema: ZodSchema<T>, preset: PresetName, overrides?: Partial<ZodValidatorOptions>): FieldValidator<T>
// Primary functions
createYupValidator<T>(schema: yup.Schema<T>, options?: YupValidatorOptions): FieldValidator<T>
createYupStrictValidator<T>(schema: yup.Schema<T>, options?: Omit<YupValidatorOptions, 'stripUnknown'>): FieldValidator<T>
createYupLenientValidator<T>(schema: yup.Schema<T>, options?: Omit<YupValidatorOptions, 'stripUnknown'>): FieldValidator<T>
// Preset helpers
createYupValidatorWithPreset<T>(schema: yup.Schema<T>, preset: PresetName, overrides?: Partial<YupValidatorOptions>): FieldValidator<T>
// Primary functions
createJoiValidator<T>(schema: Joi.Schema, options?: JoiValidatorOptions): FieldValidator<T>
createJoiStrictValidator<T>(schema: Joi.Schema, options?: Omit<JoiValidatorOptions, 'allowUnknown'>): FieldValidator<T>
createJoiLenientValidator<T>(schema: Joi.Schema, options?: Omit<JoiValidatorOptions, 'allowUnknown'>): FieldValidator<T>
// Preset helpers
createJoiValidatorWithPreset<T>(schema: Joi.Schema, preset: PresetName, overrides?: Partial<JoiValidatorOptions>): FieldValidator<T>
Form Validators
createZodFormValidator<T>(schema: ZodSchema<T>, options?: ZodValidatorOptions): FormValidator<T>
createZodFormValidatorWithFieldErrors<T>(schema: ZodSchema<T>, options?: ZodValidatorOptions): FieldErrorsValidator<T>
createZodSyncFormValidator<T>(schema: ZodSchema<T>, options?: Omit<ZodValidatorOptions, 'parseMode'>): FormValidator<T>
createZodAsyncFormValidator<T>(schema: ZodSchema<T>, options?: Omit<ZodValidatorOptions, 'parseMode'>): FormValidator<T>
createYupFormValidator<T>(schema: yup.Schema<T>, options?: YupValidatorOptions): FormValidator<T>
createYupFormValidatorWithFieldErrors<T>(schema: yup.Schema<T>, options?: YupValidatorOptions): FieldErrorsValidator<T>
createYupStrictFormValidator<T>(schema: yup.Schema<T>, options?: Omit<YupValidatorOptions, 'stripUnknown'>): FormValidator<T>
createYupLenientFormValidator<T>(schema: yup.Schema<T>, options?: Omit<YupValidatorOptions, 'stripUnknown'>): FormValidator<T>
createJoiFormValidator<T>(schema: Joi.Schema, options?: JoiValidatorOptions): FormValidator<T>
createJoiFormValidatorWithFieldErrors<T>(schema: Joi.Schema, options?: JoiValidatorOptions): FieldErrorsValidator<T>
createJoiStrictFormValidator<T>(schema: Joi.Schema, options?: Omit<JoiValidatorOptions, 'allowUnknown'>): FormValidator<T>
createJoiLenientFormValidator<T>(schema: Joi.Schema, options?: Omit<JoiValidatorOptions, 'allowUnknown'>): FormValidator<T>
Options
interface ZodValidatorOptions {
parseMode?: 'sync' | 'async';
errorTransform?: (issues: ZodIssue[]) => string;
pathFormatter?: (path: (string | number)[]) => string;
abortEarly?: boolean;
}
interface YupValidatorOptions {
context?: Record<string, any>;
stripUnknown?: boolean;
errorTransform?: (errors: any[]) => string;
pathFormatter?: (path: (string | number)[]) => string;
abortEarly?: boolean;
}
interface JoiValidatorOptions {
context?: Record<string, any>;
allowUnknown?: boolean;
errorTransform?: (details: any[]) => string;
pathFormatter?: (path: (string | number)[]) => string;
abortEarly?: boolean;
}
Mixing Validators: You can combine validation adapters with Rilay's built-in validators in the same field's validators
array. They will be executed in order.
Performance: Use the fast
preset or abortEarly: true
for forms with many validation rules to improve performance by stopping at the first error.
Library Installation: Remember to install the validation library you want to use (zod
, yup
, or joi
) alongside the adapters package. The adapters are designed to work without hard dependencies.