Validation
Learn about auto-derived validation, custom validators, and async validation in BuzzForm.
Validation
BuzzForm has two layers of validation:
- Auto-derived validators — generated from field properties like
required,minLength,min, etc. - Custom validators — defined in the
validateconfig for additional rules
Auto-Derived Validation
Validation is automatically generated from field properties:
const contactSchema = defineSchema({
fields: [
{
type: "text",
name: "name",
label: "Name",
required: true, // → adds "required" validator
minLength: 2, // → adds "minLength" validator
maxLength: 50, // → adds "maxLength" validator
},
{
type: "email",
name: "email",
label: "Email",
required: true, // → adds "email" validator
},
{
type: "textarea",
name: "message",
label: "Message",
required: true,
minLength: 10, // → adds "minLength" validator
maxLength: 500, // → adds "maxLength" validator
},
],
});You don't need to manually specify these in validate — they're derived automatically.
Validation by Field Type
| Field Type | Auto-Derived Validators |
|---|---|
| All fields | required (if required: true) |
text, textarea, password | minLength, maxLength, pattern |
email | email |
password | passwordCriteria (if criteria set) |
number | min, max, precision, step |
date | minDate, maxDate |
tags | minTags, maxTags |
array | minItems, maxItems |
select (hasMany) | minSelected, maxSelected |
Custom Validation
Use the validate property for explicit validation rules beyond auto-derived validators. Checks are grouped by trigger:
const contactSchema = defineSchema({
fields: [
{
type: "text",
name: "name",
label: "Name",
required: true,
minLength: 2,
validate: {
onBlur: {
checks: [
{
type: "pattern",
message: "Only letters and spaces allowed",
args: { pattern: "^[a-zA-Z\\s]+$" },
},
],
},
},
},
{
type: "email",
name: "email",
label: "Email",
required: true,
},
{
type: "textarea",
name: "message",
label: "Message",
required: true,
minLength: 10,
validate: {
onSubmit: {
checks: [
{
type: "pattern",
message: "Message must contain at least one question",
args: { pattern: ".*\\?.*" },
},
],
},
},
},
],
});Validation Triggers
onChange— runs when the field value changesonBlur— runs when the field loses focusonSubmit— runs when the form is submitted
Async Validation
Use onBlur or onChange with async validators for server-side checks:
const registerSchema = defineSchema({
fields: [
{
type: "text",
name: "username",
label: "Username",
required: true,
validate: {
onBlur: {
debounceMs: 500, // Wait 500ms after user stops typing
checks: [
{
type: "usernameAvailable",
message: "This username is already taken",
},
],
},
},
},
],
});First, register the custom validator using defineValidators:
import { defineValidators } from "@buildnbuzz/form-react";
const customValidators = defineValidators({
usernameAvailable: async (value: unknown) => {
if (typeof value !== "string") return false;
const response = await fetch(`/api/check-username?username=${value}`);
const data = await response.json();
return data.available ? true : "Username is already taken";
},
});
const form = useForm({
schema: registerSchema,
registries: { validators: customValidators },
});Use defineValidators to create type-safe custom validators. It provides proper typing for validator functions and ensures they return the correct format (true for valid, string for error message).
Dynamic Validation
BuzzForm supports full reactivity in validation. Both validator arguments and error messages can be dynamic expressions (Expr<T>).
Dynamic Validator Arguments
You can pass form data or context directly into validator arguments:
{
type: "minLength",
message: "Too short",
// Re-validates whenever "/minChars" changes
args: {
min: { $data: "/minChars" }
}
}Dynamic Messages
Error messages can use template interpolation and conditional logic. They have access to data, context, and the resolved args of the current validator:
{
type: "minLength",
// Use resolved args in the message
message: { $text: "Must be at least ${/args/min} characters." },
args: { min: { $data: "/limit" } }
}You can even use branching logic for messages:
{
type: "required",
message: {
$when: { $data: "/isUrgent", eq: true },
$then: "STOP! This is urgent and required.",
$else: "Please provide a value."
},
args: { isRequired: true }
}Cross-Field Validation
Use { $data: "/path" } to reference other fields:
const registerSchema = defineSchema({
fields: [
{ type: "password", name: "password", label: "Password", required: true },
{
type: "password",
name: "confirmPassword",
label: "Confirm Password",
required: true,
validate: {
onSubmit: {
checks: [
{
type: "matches",
message: "Passwords do not match",
args: { other: { $data: "/password" } },
},
],
},
},
},
],
});Important: Use JSON Pointer format (paths start with /). See Dynamic Values for details.
Built-in Validators
BuzzForm provides these built-in validators:
Prop
Type
Custom Validator Registry
Register custom validators globally using defineValidators:
import { defineValidators } from "@buildnbuzz/form-react";
// Define your custom validators with proper typing
const customValidators = defineValidators({
usernameAvailable: async (value: unknown) => {
if (typeof value !== "string") return false;
const available = await checkUsernameAvailable(value);
return available;
},
validatePromoCode: async (value: unknown) => {
if (typeof value !== "string") return false;
const valid = await validatePromo(value);
return valid;
},
});
// Pass to useForm
const form = useForm({
schema: contactSchema,
registries: { validators: customValidators },
});Then use them in your schema:
const contactSchema = defineSchema({
fields: [
{
type: "text",
name: "username",
validate: {
onBlur: {
checks: [
{
type: "usernameAvailable",
message: "This username is taken",
},
],
},
},
},
],
});Form-Level Validation
Add validation at the form level for complex rules involving multiple fields:
const contactSchema = defineSchema({
fields: [
{ type: "text", name: "name", label: "Name" },
{ type: "email", name: "email", label: "Email" },
],
validate: {
onSubmit: {
checks: [
{
type: "namesUnique",
message: "All names must be unique",
args: {
fields: [{ $data: "/name" }, { $data: "/email" }],
},
},
],
},
},
});Derived Validation Mode
Control when auto-derived validators run:
const form = useForm({
schema,
derivedValidationMode: "blur", // or "change" or "submit"
});"blur"(default) — derived validators run on blur and submit"change"— derived validators run on change, blur, and submit"submit"— derived validators only run on submit
Complete Example
Building on the Quick Start contact form:
import { defineSchema, type InferType } from "@buildnbuzz/form-react";
export const contactSchema = defineSchema({
title: "Contact Form",
fields: [
{
type: "text",
name: "name",
label: "Full Name",
placeholder: "John Doe",
required: true,
minLength: 2,
maxLength: 50,
validate: {
onBlur: {
checks: [
{
type: "pattern",
message: "Only letters and spaces allowed",
args: { pattern: "^[a-zA-Z\\s]+$" },
},
],
},
},
},
{
type: "email",
name: "email",
label: "Email",
placeholder: "john@example.com",
required: true,
},
{
type: "textarea",
name: "message",
label: "Message",
placeholder: "How can we help?",
required: true,
minLength: 10,
maxLength: 500,
description: "Be as detailed as possible so we can help you better.",
},
],
});
export type ContactData = InferType<typeof contactSchema.fields>;Related
- Quick Start — Build a form with basic validation
- Schema — Define schemas with validation properties
- Dynamic Values — Use
$dataand$contextin validation args - Custom Fields — Build custom validators