How Validation Works
Hyvä Form Builder validates a submission in the visitor's browser and again on the server, from one set of rules. This page explains the design choices behind that, what a visitor sees when a field fails, what the submission endpoint answers, and which parts you extend. Read it before you build a custom field, a custom validator or a custom form root.
One Rule Source for the Browser and the Server
Every Form Builder field declares its validation rules once, in its field descriptor. The
descriptor implements FieldDescriptorInterface and is registered in FieldDescriptorPool
by component name. The same rules then reach two places: the storefront's Alpine component
reads them from the page's initial state, and the server compiles them into the validation
schema on every submission, from the stored form tree and never from the request.
The browser applies the rules in the same order and with the same message phrases as the server, so a failure caught before Send looks exactly like a server rejection. There is no second place to declare a rule, which is what keeps the two sides from drifting.
The server is the authority on every rule
The browser check saves a round trip and nothing more. Where the browser and the server disagree, the server's verdict stands, and a custom rule that the browser knows nothing about is still enforced.
To extend: a new field type ships a descriptor, see
Create a Custom Field. A new rule for a type ships a validator
registered in FieldValidatorPool, see Add Custom Validation.
Alpine Owns Client-Side Validation Once Hydrated
Form Builder field templates emit the native constraint attributes, required, type,
pattern, min, max, minlength and maxlength. Until the Alpine component has
hydrated, the browser applies them and shows its own validation bubbles. On hydration the
component switches the browser's validation off for that form, and from then on the Alpine
rule engine is the only client-side validator and runs on every Send.
Native browser validation was the first channel, and Form Builder moved away from it as the primary one for concrete reasons:
- It cannot express every rule. Native
requiredon a grouped checkbox means "tick this box", so "tick at least one" has no native form, and neither do minimum and maximum selections, closed-list membership on a Multi-Choice, or any rule a custom validator adds. Those rules can only live in the engine, and the engine has to be reachable for them to work. - It silences everything else. While any control is natively invalid the browser refuses to
fire the
submitevent, so an engine bound to that event never runs. A visitor gets one bubble for the first native failure and nothing about the rest. One channel flags every failing field at once. - It breaks multi-step forms. Inactive steps stay in the DOM hidden, and the browser will refuse to submit while pointing at a control the visitor cannot see, a dead Send button. The engine validates per step and switches back to the step that holds the error.
- It is not the storefront's voice. Bubbles follow the browser's locale, cannot be styled to match the theme, and vanish on the next click. Engine messages are Magento translatable phrases, rendered in the theme's own style, and they persist until the field is fixed.
- It never agreed with the server. Native rules speak a different vocabulary,
stepas a grid rather than decimal places, an unanchored client pattern against an anchored server one. The engine runs the same rule set with the same messages the server uses, so a value that passes pre-flight is not bounced by the server for a rule the browser could have checked.
Native validation still has one job, and keeps it: the pre-hydration window and visitors without JavaScript.
The switch happens on hydration and not through a static novalidate attribute on purpose.
A static attribute would also remove native checks for visitors without JavaScript and for
anyone who submits before scripts have loaded. Switching on hydration keeps instant feedback
on those paths. The native attributes stay in the markup for assistive technology and for the
:required pseudo-class.
Without JavaScript, the browser validates as it always has. An invalid submission that still reaches the server is answered with a standalone HTML result page listing each field's label and error.
Bespoke Validators and Custom Type Tokens
A custom type token, registered as described in Add Custom Validation, is enforced on the server only. The browser has no rule set for a token it does not know, so it applies the text-style checks from the rules the descriptor emits and lets the submission through. The visitor still sees your message, because the 400 envelope paints it under the field.
Related Topics
- Architecture - the map of every Form Builder extension surface.
- Add Custom Validation - register a server validator for a custom type token.
- Create a Custom Field - build a field and the descriptor whose rules feed validation.
- Create a Custom Form Root - ship a root, and its script, that keeps this validation contract.
- Building Forms - the field settings you configure in the editor.