Build a custom validator
If the built-in rules are not enough, register your own validator when Formie mounts a form. Each mounted form gets its own validator instance, exposed through formie:validator:ready.
1. Add the rule to your field markup
Custom validators still use the same data-formie-validation payload as the built-in rules.
Direct markup
<div
data-formie-field-handle="workEmail"
data-formie-validation='[
{ "type": "company-email", "domain": "verbb.io" }
]'
>
...
</div>Custom PHP field
If you are building a custom field in PHP, the normal pattern is to add browser validation rules from defineValidationRules(). Formie's field wrapper will then emit data-formie-validation for you automatically.
protected function defineValidationRules(): array
{
$validators = parent::defineValidationRules();
$validators[] = ['type' => 'company-email'];
return $validators;
}That is the same pattern Formie's own fields use for rules like email and number.
If your custom validator needs extra payload options beyond Formie's standard normalized keys, override validationRules() instead. The default normalization currently only keeps type, fieldId, fieldHandle, min, and max.
public function validationRules(): array
{
return array_merge(parent::validationRules(), [
[
'type' => 'company-email',
'domain' => 'verbb.io',
],
]);
}2. Register the validator
document.addEventListener('formie:validator:ready', (event) => {
const { validator } = event.detail;
validator.addValidator(
'company-email',
({ input, getRule }) => {
const rule = getRule('company-email');
// Every validator runs across Formie's validation inputs, so opt out early
// when this field does not declare the custom rule.
if (!rule || !input.value) {
return true;
}
const domain = rule !== true && typeof rule === 'object' && typeof rule.domain === 'string'
? rule.domain
: 'example.com';
return input.value.toLowerCase().endsWith(`@${domain.toLowerCase()}`);
},
({ label, getRule }) => {
const rule = getRule('company-email');
const domain = rule !== true && rule && typeof rule === 'object' && typeof rule.domain === 'string'
? rule.domain
: 'example.com';
return `${label} must use an @${domain} address.`;
},
);
});3. Use the validation context
Your validator callback receives a context object with:
inputfor the current input elementlabelfor the field label textfieldfor the[data-formie-field-handle]wrapperformfor the mounted formrulesfor the parsed field rulesgetRule(name)for looking up one rule by namet(message, replacements)for translated messages
In practice, getRule() is usually the most important part because it lets one validator read its own payload options.
Replace or remove a rule
If you need to swap out behavior for a mounted form, register the same name again or remove it:
document.addEventListener('formie:validator:ready', (event) => {
const { validator } = event.detail;
validator.removeValidator('company-email');
});Tips
- Bail out early when your rule is not declared on the current field.
- Put rule options into the
data-formie-validationJSON instead of hard-coding them in JavaScript. - Resolve related fields by Formie field handle for consistency with the rest of Formie's browser behavior.