Skip to content

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:

Live Preview

Fails if the value is empty or whitespace-only.

<div data-form-field="name"
data-validate='[{"type":"NotEmpty","options":{"message":"This field is required"}}]'>

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"}}
]'>
OptionTypeDescription
minimumnumberMinimum length
maximumnumberMaximum length
messagestringError message

Validates email format. Passes on empty values.

<div data-form-field="email"
data-validate='[{"type":"EmailAddress","options":{"message":"Invalid email address"}}]'>

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"}}]'>

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"}}]'>

Validates that the value is a whole number.

<div data-form-field="count"
data-validate='[{"type":"Integer","options":{"message":"Must be a whole 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"}}]'>

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"}}]'>

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"}}]'>

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"}}]'>

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>

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>

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',
};
},
});

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;
},
});

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.