Validation
Validation rules are declared in HTML via the data-validate attribute as a JSON array. Each rule has a type (matching a registered validator) and optional options.
<div data-form-field="age" data-validate='[ {"type":"NotEmpty","options":{"message":"Age is required"}}, {"type":"Integer","options":{"message":"Must be a whole number"}}, {"type":"NumberRange","options":{"minimum":18,"maximum":120,"message":"Must be between 18 and 120"}} ]'> <label for="age">Age</label> <input id="age" type="text" name="age" /> <div id="age-errors" class="invalid-feedback"></div></div>Try each validator below — leave fields empty or enter invalid data to see the error messages:
Built-in Validators
Section titled “Built-in Validators”NotEmpty
Section titled “NotEmpty”Fails if the value is empty or whitespace-only.
<div data-form-field="name" data-validate='[{"type":"NotEmpty","options":{"message":"This field is required"}}]'>StringLength
Section titled “StringLength”Validates minimum and/or maximum string length. Passes on empty values (combine with NotEmpty for required fields).
<div data-form-field="bio" data-validate='[ {"type":"StringLength","options":{"minimum":10,"maximum":500,"message":"Must be 10–500 characters"}} ]'>| Option | Type | Description |
|---|---|---|
minimum | number | Minimum length |
maximum | number | Maximum length |
message | string | Error message |
EmailAddress
Section titled “EmailAddress”Validates email format. Passes on empty values.
<div data-form-field="email" data-validate='[{"type":"EmailAddress","options":{"message":"Invalid email address"}}]'>RegularExpression
Section titled “RegularExpression”Tests against a regular expression. Pattern uses PCRE-style delimiters (/pattern/flags) as used by TYPO3/PHP.
<div data-form-field="code" data-validate='[{"type":"RegularExpression","options":{"regularExpression":"/^[A-Z]{3}-\\d{4}$/","message":"Format: ABC-1234"}}]'>NumberRange
Section titled “NumberRange”Validates that a numeric value falls within a range.
<div data-form-field="quantity" data-validate='[{"type":"NumberRange","options":{"minimum":1,"maximum":99,"message":"Enter 1–99"}}]'>Integer
Section titled “Integer”Validates that the value is a whole number.
<div data-form-field="count" data-validate='[{"type":"Integer","options":{"message":"Must be a whole number"}}]'>Float / Number
Section titled “Float / Number”Validates that the value is a valid decimal/number.
<div data-form-field="price" data-validate='[{"type":"Float","options":{"message":"Must be a valid number"}}]'>Alphanumeric
Section titled “Alphanumeric”Only allows Unicode letters and numbers (\p{L}\p{N}).
<div data-form-field="username" data-validate='[{"type":"Alphanumeric","options":{"message":"Letters and numbers only"}}]'>DateTime
Section titled “DateTime”Validates that the value can be parsed as a date.
<div data-form-field="birthday" data-validate='[{"type":"DateTime","options":{"message":"Enter a valid date"}}]'>DateRange
Section titled “DateRange”Validates that a date falls within a range.
<div data-form-field="eventDate" data-validate='[{"type":"DateRange","options":{"minimum":"2024-01-01","maximum":"2025-12-31","message":"Date must be in 2024–2025"}}]'>FileSize
Section titled “FileSize”Validates uploaded file sizes. Supports units like 1K, 5M, 1G.
<div data-form-field="avatar" data-validate='[{"type":"FileSize","options":{"minimum":"1K","maximum":"5M","message":"File must be 1KB–5MB"}}]'> <input type="file" name="avatar" /></div>Custom Validators
Section titled “Custom Validators”Register a custom validator before initializing forms:
import { registerDefaultValidators, registerValidator } from 'formlayer';
registerDefaultValidators();
registerValidator({ type: 'PasswordStrength', validate(value, options) { if (!value) return { valid: true, message: '' };
const hasUpper = /[A-Z]/.test(value); const hasLower = /[a-z]/.test(value); const hasNumber = /\d/.test(value); const hasSpecial = /[!@#$%^&*]/.test(value); const strong = hasUpper && hasLower && hasNumber && hasSpecial && value.length >= 8;
return { valid: strong, message: strong ? '' : (options['message'] as string) ?? 'Password is too weak', }; },});Then use it in HTML:
<div data-form-field="password" data-validate='[ {"type":"NotEmpty","options":{"message":"Password is required"}}, {"type":"PasswordStrength","options":{"message":"Use 8+ chars with upper, lower, number, and special"}} ]'> <label for="password">Password</label> <input id="password" type="password" name="password" /> <div id="password-errors" class="invalid-feedback"></div></div>Async Validators
Section titled “Async Validators”Validators can return a Promise for server-side checks:
registerValidator({ type: 'UniqueEmail', async validate(value, options) { if (!value) return { valid: true, message: '' };
const response = await fetch(`/api/check-email?email=${encodeURIComponent(value)}`); const data = await response.json();
return { valid: data.available, message: data.available ? '' : (options['message'] as string) ?? 'Email already taken', }; },});Custom Validation Logic per Field
Section titled “Custom Validation Logic per Field”Override the validation pipeline for a specific field using FieldControllerOptions:
import { initField } from 'formlayer';
const ctrl = initField(document.getElementById('confirm-password')!, { validate(value, rules, defaultValidate) { const result = defaultValidate(); if (!result.isValid) return result;
const password = document.getElementById('password') as HTMLInputElement; if (value !== password.value) { return { isValid: false, errors: ['Passwords do not match'] }; } return result; },});Error Rendering
Section titled “Error Rendering”Validation produces error messages; FormLayer renders them into the DOM separately. By default, messages are escaped and joined with <br/> inside #input-id-errors or .invalid-feedback.
To change the container location, add icons, or customize markup, see Error Rendering.