React Form Validation with Zod
Form validation is one of the clearest reasons to use FormEngine. Instead of spreading validation logic across React components, effects, and error state, you define rules in JSON and let the runtime execute them. Under the hood, FormEngine uses Zod for built-in validation behavior. If you want to validate forms without adding more component-level wiring, this page is the core reference.How validation works in FormEngine
FormEngine validates fields from the schema:- a field declares its rules in
schema.validations - validators run in the order they appear
- if a validator fails, FormEngine returns and displays the error
- the runtime handles error state and field updates for you
Quick example
This field validates required input, email format, and maximum length:Validated email field
Real working example from the FormEngine examples
The booking form used in the Next.js, Remix, and Formik examples applies custom validators directly in the real schema:Booking form validation from the examples
- a business-specific text rule
- a date rule
- a numeric rule

Built-in validators
FormEngine includes built-in validators grouped by the type of value being validated.String validators
| Validator | Purpose | Example args |
|---|---|---|
required | Field must not be empty | { "message": "Required" } |
nonEmpty | Alias for required | { "message": "Cannot be empty" } |
min | Minimum character length | { "limit": 3, "message": "Too short" } |
max | Maximum character length | { "limit": 100, "message": "Too long" } |
length | Exact character count | { "length": 10 } |
email | Must be a valid email | { "message": "Invalid email" } |
url | Must be a valid URL | { "message": "Invalid URL" } |
uuid | Must be a valid UUID | { "message": "Invalid UUID" } |
ip | Must be a valid IP address | { "message": "Invalid IP" } |
datetime | Must be a valid ISO datetime | { "message": "Invalid datetime" } |
regex | Must match a pattern | { "regex": "^[A-Z]", "message": "Must start with uppercase" } |
includes | Must contain a substring | { "value": "@company.com" } |
startsWith | Must start with a string | { "value": "https://" } |
endsWith | Must end with a string | { "value": ".com" } |
Number validators
| Validator | Purpose | Example args |
|---|---|---|
required | Must have a value | { "message": "Required" } |
min | Minimum value | { "limit": 0, "message": "Too small" } |
max | Maximum value | { "limit": 999 } |
lessThan | Strictly less than a value | { "value": 100 } |
moreThan | Strictly greater than a value | { "value": 0 } |
integer | Must be a whole number | { "message": "Whole numbers only" } |
multipleOf | Must be a multiple of a value | { "value": 5 } |
Date, array, and boolean validators
- date fields support
required,min, andmax - array fields support
required,nonEmpty,min,max, andlength - boolean fields support
required,truthy, andfalsy
Custom validators
When built-in rules are not enough, add a custom validator withfnSource:
Custom validator
true when the field is valid. Return a string when it is invalid.
Use this when:
- the rule is specific to your domain
- built-in validators are not expressive enough
- you need access to more form context
isFullName and dateInTheFuture.
Cross-field validation
For rules like “confirm password must match password”, useform.data inside a custom validator:
Password confirmation
Async validation
For server-backed checks such as uniqueness validation, use an async custom validator:Async validation example
- email uniqueness checks
- username availability
- organization-specific policies
- external validation APIs
Form-level validation on submit
Field validation is not the whole story. You can also validate the full form when the user clicks submit:Submit button with validation
failOnError is true, FormEngine stops the action chain until the validation passes.
When to disable automatic validation
Validation runs automatically by default. If you want validation only on submit, setautoValidate to false:
Validation only on submit
Validation in the Designer
If your team uses the Designer, validation rules can also be configured visually. That gives teams a path from schema-driven validation to visual editing without changing the underlying model. See Designer validation if your workflow depends on the builder.FormEngine vs manual React validation
The main practical difference is where the validation logic lives:| Approach | Where rules live | Typical cost |
|---|---|---|
| Manual React form code | component logic and handlers | more wiring as the form grows |
| FormEngine | JSON schema | easier reuse and less repetitive glue code |
- dynamic forms
- multi-step forms
- reusable workflows
- forms shared across teams or products
FAQ
Can I show errors only after submit?
Can I show errors only after submit?
Yes. Set
autoValidate to false and trigger validation when the user submits the form.Can I validate fields against other fields?
Can I validate fields against other fields?
Yes. Use a custom validator and read related values from
form.data.Do I need to use custom validators often?
Do I need to use custom validators often?
Usually not. Built-in validators cover many common cases. Custom validators are best for domain-specific rules.
Is validation available in the visual builder too?
Is validation available in the visual builder too?
Yes. If your team uses the Designer, validation rules can also be configured there.
Next steps
Add conditional logic
Combine validation with field visibility and branching behavior.
Handle form data
Read, update, and submit the validated form state.
Build a multi-step form
Apply validation across a longer workflow.
Try it in the Online Builder
Configure validation visually and inspect the schema.