Search for a command to run...
Last updated June 9, 2026
Ctrovalidate provides a flexible, hybrid schema system that allows you to define validation rules in the format that best fits your workflow. The engine normalizes all formats automatically via normalizeRules().
ValidationSchemaThe top-level type mapping field names to their rules:
type ValidationSchema = Record<string, SchemaRule>;SchemaRuleA single field's rules. Can be a string or an array of strings and/or RuleDefinition objects:
type SchemaRule = string | (string | RuleDefinition)[];RuleDefinitionThe normalized object representation:
interface RuleDefinition {
name: string;
params: unknown[];
}The most common format. Rules are separated by | and parameters by :.
// Single field's rules
const fieldRules = 'required|minLength:8|email';
// Full schema
const schema = {
email: 'required|email',
password: 'required|minLength:8|strongPassword',
age: 'required|numeric|between:18,120',
};Useful when rule parameters contain special characters (commas, pipes) that would break string parsing.
const schema = {
password: [
'required',
{ name: 'minLength', params: ['8'] },
{ name: 'regex', params: ['^(?=.*[A-Z])'] },
],
};Mix strings and objects freely within the array:
const schema = {
username: ['required', 'alphaNum', { name: 'minLength', params: ['3'] }],
};Register reusable rule macros via the browser adapter's Ctrovalidate.defineAlias():
import { Ctrovalidate } from 'ctrovalidate-browser';
Ctrovalidate.defineAlias('securePassword', 'required|minLength:8|strongPassword');<input data-ctrovalidate-rules="securePassword" />Aliases expand recursively during normalizeRules(). Circular references are detected and blocked via a Set-based cycle detector.
For server-side usage, pass aliases as a ValidationOptions parameter:
import { validateValue } from 'ctrovalidate-core';
validateValue('MyPass1!', 'securePassword', {
aliases: { securePassword: 'required|minLength:8|strongPassword' },
});The sameAs rule compares a value against a target. In ctrovalidate-browser, the adapter resolves field references using the DOM (form.querySelector('[name="fieldName"]').value) before delegating to the core rule.
<input name="password" data-ctrovalidate-rules="required" />
<input name="confirm" data-ctrovalidate-rules="required|sameAs:password" />In the core package or framework hooks, pass the target value directly as a parameter:
import { validateValue } from 'ctrovalidate-core';
validateValue('abc', 'sameAs:abc'); // passes
validateValue('abc', 'sameAs:xyz'); // failsRules are executed in the order they are defined. First-fail-wins — when a rule fails, execution stops for that field and the error is returned immediately.
// required runs first, then email. If required fails, email is never evaluated.
'required|email'Error messages are resolved in this priority order (highest to lowest):
messages[ruleName] — from ValidationOptions.messages (or Ctrovalidate.setCustomMessages() in browser)messages['*'] — catch-all from optionstranslator.translate(ruleName, params, locale) — from the i18n system'Invalid input.' — hardcoded fallbackDefine your schema once and use it across client and server:
// lib/schemas.ts
export const signupSchema = {
email: 'required|email',
password: 'required|minLength:8',
};
// React (client)
import { useCtrovalidate } from 'ctrovalidate-react';
const { errors } = useCtrovalidate({ schema: signupSchema });
// Next.js (server)
import { validateAction } from 'ctrovalidate-next';
const { isValid } = await validateAction(formData, signupSchema);