API Reference
Validation
NovaForms provides a flexible and powerful validation system to ensure data integrity and guide users through your forms. You can implement validation using built-in features, regex patterns, constraints, and custom functions.
Overview
NovaForms supports several layers of validation:
- Built-in Validation: Automatic validation for specific field types (e.g.,
email,url,number). - Pattern Validation: Using regular expressions (
regex) to enforce specific input formats. - Constraint Validation: Defining
min,max,minLength,maxLength,maxSizefor various field types. - Required Field Validation: Marking fields as mandatory.
- Custom Validation: Implementing complex, dynamic, or cross-field validation logic.
Built-in Validation
Several field types come with automatic, built-in validation to ensure common data formats are correct.
Email Fields
type: "email" fields automatically validate for a basic email format.
{
name: "userEmail",
type: "email",
title: "Your Email",
required: true
}
URL Fields
type: "url" fields automatically validate for a basic URL format.
{
name: "websiteUrl",
type: "url",
title: "Company Website"
}
Number Fields
type: "number" fields respect min, max, and step properties for numeric range validation.
{
name: "age",
type: "number",
title: "Age",
min: 18,
max: 99,
step: 1,
required: true
}
Date Fields
type: "date" and type: "datetime" fields respect minDate and maxDate properties.
{
name: "eventDate",
type: "date",
title: "Event Date",
minDate: "2023-01-01", // ISO 8601 format
maxDate: "2024-12-31"
}
Pattern Validation (Regex)
For more specific format requirements, you can use regular expressions (regex) with the pattern property on any text-based field (string, text, email, tel, url).
The pattern property accepts an array of objects, allowing you to define multiple patterns, each with its own error message.
Basic Pattern
{
name: "productCode",
type: "string",
title: "Product Code",
pattern: [
{
regex: "^[A-Z]{3}-\\d{4}$", // e.g., ABC-1234
message: "Product code must be 3 uppercase letters followed by a dash and 4 digits."
}
],
required: true
}
Multiple Patterns
When multiple patterns are provided, the field is valid only if all patterns match.
{
name: "strongPassword",
type: "string",
title: "Password",
pattern: [
{ regex: ".{8,}", message: "Must be at least 8 characters long." },
{ regex: "[0-9]", message: "Must contain at least one digit." },
{ regex: "[a-z]", message: "Must contain at least one lowercase letter." },
{ regex: "[A-Z]", message: "Must contain at least one uppercase letter." },
{ regex: "[^a-zA-Z0-9]", message: "Must contain at least one special character." }
],
required: true
}
Common Regex Patterns
Here are some common regex patterns you might find useful:
Phone Numbers
US Phone Number (XXX-XXX-XXXX)
{
name: "usPhone",
type: "tel",
title: "US Phone Number",
pattern: [{ regex: "^\\d{3}-\\d{3}-\\d{4}$", message: "Format: XXX-XXX-XXXX" }]
}
International Phone Number (simple)
{
name: "intlPhone",
type: "tel",
title: "International Phone",
pattern: [{ regex: "^\\+?[0-9]{6,14}$", message: "Enter a valid international phone number." }]
}
Passwords
Strong Password (8+ chars, upper, lower, digit, special)
{
name: "password",
type: "string",
title: "Password",
pattern: [
{ regex: ".{8,}", message: "Min 8 characters" },
{ regex: "[0-9]", message: "At least one digit" },
{ regex: "[a-z]", message: "At least one lowercase" },
{ regex: "[A-Z]", message: "At least one uppercase" },
{ regex: "[^a-zA-Z0-9]", message: "At least one special character" }
],
required: true
}
Credit Cards
Visa Card (13 or 16 digits, starts with 4)
{
name: "visaCard",
type: "string",
title: "Visa Card Number",
pattern: [{ regex: "^4[0-9]{12}(?:[0-9]{3})?$", message: "Invalid Visa card number." }]
}
Mastercard (16 digits, starts with 51-55)
{
name: "masterCard",
type: "string",
title: "Mastercard Number",
pattern: [{ regex: "^5[1-5][0-9]{14}$", message: "Invalid Mastercard number." }]
}
American Express (15 digits, starts with 34 or 37)
{
name: "amexCard",
type: "string",
title: "Amex Card Number",
pattern: [{ regex: "^3[47][0-9]{13}$", message: "Invalid American Express card number." }]
}
Postal Codes
US ZIP Code (5 digits or 5+4)
{
name: "usZip",
type: "string",
title: "US ZIP Code",
pattern: [{ regex: "^\\d{5}(?:[-\\s]\\d{4})?$", message: "Invalid US ZIP code." }]
}
Canadian Postal Code (A1A 1A1)
{
name: "caPostal",
type: "string",
title: "Canadian Postal Code",
pattern: [{ regex: "^[A-Za-z]\\d[A-Za-z][ -]?\\d[A-Za-z]\\d$", message: "Invalid Canadian postal code." }]
}
UK Postcode (various formats)
{
name: "ukPostcode",
type: "string",
title: "UK Postcode",
pattern: [{ regex: "^([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([A-Za-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9][A-Za-z]?))))\\s?[0-9][A-Za-z]{2})$", message: "Invalid UK postcode." }]
}
Social Security Number (SSN)
US SSN (XXX-XX-XXXX)
{
name: "ssn",
type: "string",
title: "Social Security Number",
pattern: [{ regex: "^(?!000|666)[0-8][0-9]{2}-(?!00)[0-9]{2}-(?!0000)[0-9]{4}$", message: "Invalid SSN format." }]
}
Product Codes
ISBN-10 or ISBN-13
{
name: "isbn",
type: "string",
title: "ISBN",
pattern: [{ regex: "^(?:ISBN(?:-13)?:?)(?=[0-9]{13}$)([0-9]{3}-){2}[0-9]{3}[0-9X]$|^[0-9]{9}[0-9X]$", message: "Invalid ISBN format." }]
}
Constraint Validation
Beyond regex patterns, NovaForms supports various constraints to validate field values.
Numeric Constraints (number, currency, rating, scale)
min: Minimum allowed value.max: Maximum allowed value.step: Valid step interval for the value.
{
name: "quantity",
type: "number",
title: "Quantity",
min: 1,
max: 100,
step: 1
}
String Length Constraints (string, text)
minLength: Minimum number of characters.maxLength: Maximum number of characters.
{
name: "username",
type: "string",
title: "Username",
minLength: 3,
maxLength: 20
}
File Constraints (file, fileV2, uploadToBase)
maxSize: Maximum file size in bytes.accept: Comma-separated list of accepted file types (e.g.,"image/*",".pdf,.doc").
{
name: "documentUpload",
type: "file",
title: "Upload Document",
maxSize: 5 * 1024 * 1024, // 5 MB
accept: ".pdf,.doc,.docx"
}
Date Constraints (date, datetime)
minDate: Earliest allowed date (ISO 8601 string).maxDate: Latest allowed date (ISO 8601 string).
{
name: "bookingDate",
type: "date",
title: "Booking Date",
minDate: "2024-01-01",
maxDate: "2024-12-31"
}
Required Field Validation
The required: true property ensures that a field must have a non-empty value.
{
name: "fullName",
type: "string",
title: "Full Name",
required: true
}
Conditional Required Fields
You can make a field required based on conditions using the rules system (see Rules & Effects).
const rules = [
{
name: "makeShippingRequired",
effects: [
{
targetField: "shippingAddress",
prop: "required",
type: "replace",
value: true
}
]
},
{
name: "makeShippingOptional",
effects: [
{
targetField: "shippingAddress",
prop: "required",
type: "replace",
value: false
}
]
}
];
const fields = [
{
name: "deliveryOption",
type: "radio",
title: "Delivery Option",
options: [
{ value: "pickup", label: "Pickup" },
{ value: "delivery", label: "Delivery" }
],
triggers: [
{ rule: "makeShippingRequired", when: "equal", value: "delivery" },
{ rule: "makeShippingOptional", when: "equal", value: "pickup" }
]
},
{
name: "shippingAddress",
type: "string",
title: "Shipping Address"
// 'required' will be set by the rule
}
];
Custom Validation Functions
For validation logic that goes beyond patterns and constraints, you can implement custom validation. This typically involves using custom field components or integrating with a form-level validation library.
Field-Level Custom Validation (via Custom Fields)
When creating a Custom Field, you have full control over its validation logic.
// In your custom field component (e.g., src/components/CustomEmailField.jsx)
import React, { useState } from "react";
import { registerField } from "nova-forms";
function CustomEmailField({ field, value, onChange, theme }) {
const [error, setError] = useState("");
const validateEmail = (email) => {
if (field.required && !email) {
return "Email is required.";
}
if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
return "Please enter a valid email address.";
}
if (email && field.domainRestriction && !email.endsWith(field.domainRestriction)) {
return `Email must be from ${field.domainRestriction}.`;
}
return "";
};
const handleChange = (e) => {
const newValue = e.target.value;
const validationError = validateEmail(newValue);
setError(validationError);
onChange(e); // Pass the event up to NovaForm
};
return (
<div>
<label style={{ color: theme.label }}>{field.title}</label>
<input
type="email"
value={value || ""}
onChange={handleChange}
onBlur={handleChange} // Validate on blur too
placeholder={field.placeholder}
style={{ borderColor: error ? theme.error : theme.inputBorder }}
/>
{error && <p style={{ color: theme.error }}>{error}</p>}
{field.description && <p style={{ color: theme.description }}>{field.description}</p>}
</div>
);
}
registerField("customEmail", CustomEmailField);
// In your form schema
const fields = [
{
name: "workEmail",
type: "customEmail",
title: "Work Email",
required: true,
domainRestriction: "@mycompany.com", // Custom prop for validation
description: "Please use your company email address."
}
];
Form-Level Validation
For validation that involves the entire form data or complex inter-field dependencies, you can perform validation in your onSubmit handler.
import { useState } from "react";
import { NovaForm, createFormHandler } from "nova-forms";
const fields = [
{ name: "startDate", type: "date", title: "Start Date", required: true },
{ name: "endDate", type: "date", title: "End Date", required: true },
{ name: "reason", type: "string", title: "Reason for Leave", required: true }
];
export default function LeaveRequestForm() {
const [formData, setFormData] = useState({});
const [formErrors, setFormErrors] = useState({});
const handleChange = createFormHandler({
fields,
setState: setFormData,
});
const validateForm = (data) => {
const errors = {};
if (data.startDate && data.endDate) {
const start = new Date(data.startDate);
const end = new Date(data.endDate);
if (start > end) {
errors.endDate = "End Date cannot be before Start Date.";
}
}
// Add more form-level validation rules here
return errors;
};
const handleSubmit = (e) => {
e.preventDefault();
const errors = validateForm(formData);
if (Object.keys(errors).length > 0) {
setFormErrors(errors);
alert("Please fix the form errors.");
} else {
setFormErrors({});
console.log("Form submitted successfully:", formData);
alert("Form submitted successfully!");
}
};
// Merge field-level errors with form-level errors for display
const fieldsWithErrors = fields.map(field => ({
...field,
error: formErrors[field.name] || field.error // Prioritize formErrors
}));
return (
<form onSubmit={handleSubmit} className="max-w-2xl mx-auto p-4">
<h1 className="text-2xl font-bold mb-6">Leave Request</h1>
<NovaForm
fields={fieldsWithErrors}
onChange={handleChange}
formData={formData}
/>
<button
type="submit"
className="mt-6 px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700"
>
Submit Request
</button>
</form>
);
}
Cross-Field Validation
Cross-field validation can be handled either within custom field components (if the fields are closely related) or more commonly, at the form level in the onSubmit handler, as shown in the LeaveRequestForm example above.
Advanced Validation Patterns
Dynamic Error Messages
Error messages can be made dynamic based on the input value or other form state. This is best handled within Custom Fields.
// Example: Password strength message
// (See Custom Fields documentation for full component implementation)
{
name: "password",
type: "customPassword", // A custom field type
title: "Password",
minLength: 8,
// Custom field would dynamically update its error message based on strength
}
Conditional Validation
You might want to apply validation rules only when certain conditions are met. This can be achieved by combining the rules system with custom validation.
const rules = [
{
name: "requireReasonIfOther",
effects: [
{
targetField: "otherReasonText",
prop: "required",
type: "replace",
value: true
}
]
},
{
name: "makeReasonOptional",
effects: [
{
targetField: "otherReasonText",
prop: "required",
type: "replace",
value: false
}
]
}
];
const fields = [
{
name: "issueType",
type: "select",
title: "Type of Issue",
options: [
{ value: "bug", label: "Bug Report" },
{ value: "feature", label: "Feature Request" },
{ value: "other", label: "Other" }
],
triggers: [
{ rule: "requireReasonIfOther", when: "equal", value: "other" },
{ rule: "makeReasonOptional", when: "not equal", value: "other" }
]
},
{
name: "otherReasonText",
type: "string",
title: "Please specify",
// 'required' will be set by the rule
}
];
Error Handling and Display
NovaForms automatically displays error messages provided via the error prop on a field.
{
name: "username",
type: "string",
title: "Username",
required: true,
error: "Username is required." // This error will be displayed
}
When using pattern validation, the message property of the pattern object will be displayed as the error.
Custom Error Styling
You can customize the appearance of error messages using the theme.error property (see Theme Styling) or by overriding Tailwind classes (see Styling with Tailwind).
Best Practices
1. Use Appropriate Validation Methods
- Built-in: For standard formats (email, URL, numbers).
- Pattern: For specific string formats (phone, password, product codes).
- Constraints: For numeric ranges, string lengths, file sizes.
- Required: For mandatory fields.
- Custom: For complex logic, cross-field validation, or dynamic rules.
2. Provide Clear and Helpful Error Messages
Error messages should tell the user what is wrong and how to fix it.
// ✅ Good
{ regex: ".{8,}", message: "Password must be at least 8 characters long." }
// ❌ Avoid
{ regex: ".{8,}", message: "Invalid password." }
3. Validate Early and Often (but not too often)
- Validate on
blurfor immediate feedback. - Validate on
changefor patterns (but be careful not to annoy users). - Validate on
submitfor final checks and cross-field logic.
4. Handle Edge Cases
Consider what happens with empty inputs, invalid characters, or boundary values.
5. Keep Validation Logic Separate
For complex validation, consider separating validation functions from your component logic for better maintainability.
Troubleshooting
Common Issues
- Validation not firing:
- Ensure
required: trueis set for mandatory fields. - Check
patternregex for syntax errors. - Verify
min/maxvalues are correctly set for numeric fields. - For custom validation, ensure your
onChangehandler is correctly calling validation logic and updating theerrorprop.
- Ensure
- Incorrect error messages:
- Verify the
messageproperty in yourpatternobjects. - Ensure custom validation functions return the correct error string.
- Verify the
- Cross-field validation issues:
- Make sure your form-level validation function has access to all necessary
formData. - Ensure
formErrorsare correctly mapped back to thefieldsfor display.
- Make sure your form-level validation function has access to all necessary
Debug Tips
- Console Log
formData: In youronSubmitoronChangehandlers, log theformDatato see its current state. - Test Regex: Use online regex testers to verify your patterns.
- Isolate Validation: Temporarily simplify validation rules to pinpoint the problematic one.
- Inspect Field Props: In your React DevTools, inspect the
fieldprop passed to individual components to see iferroror other validation-related props are being set as expected.
Robust validation is crucial for creating user-friendly and reliable forms. NovaForms provides a comprehensive set of tools to implement validation, from simple required fields to complex regex patterns and custom logic.