Validation
Hosted forms validate twice: in the browser (instant feedback) and on the server (so a hostile client can't bypass it). Both layers read from the same field schema, so what you see in the builder is exactly what we enforce.
Legacy forms — the kind where you write your own HTML — don't get schema validation. You're on the hook for that yourself; we still run honeypot, captcha, and the spam stack on every submission.
Per-field options
Every input field exposes a validation block in the inspector:
{
"type": "text",
"name": "username",
"label": "Username",
"required": true,
"validation": {
"min": 3,
"max": 30,
"pattern": "^[a-z0-9_]+$",
"error_message": "3-30 chars, lowercase letters, digits, underscores."
}
}
required
Field must be present and non-empty. For checkbox, at least one option ticked. For consent, the box must be checked.
min / max
- For text-ish fields (
text,textarea,email,url,tel): character count. - For
number: numeric range. - For
date: ISO date range (min: "2024-01-01"). - For
file: file count whenmultiple: true.
pattern
A JavaScript-style regex (no leading/trailing slashes). Applied to the submitted string. Use it for custom shapes:
"pattern": "^[A-Z]{2}\\d{6}$"
Bad regex doesn't crash submissions — we log it and skip the rule.
error_message
Override the default error string. Plain text, shown next to the field on failure.
File validation
file fields have their own rules on top of min/max:
- MIME type — comes from the field's
acceptarray. Anything not on the list is rejected before upload. - Per-file size — capped per plan (5 MB Free, 25 MB Pro, 100 MB Team).
- Total upload size — same cap, summed across
multipleuploads.
If a file is rejected, the submission fails — partial uploads don't land in your inbox. See Files → for the full table.
Multi-step gating
When a form has multiple steps, each Next button validates only the fields on that step. Visitors can't advance with errors pending. The final Submit validates everything — including any field they may have skipped past via browser-back.
If a field is hidden by a visibility rule, it's skipped entirely. Hidden = not validated, not submitted.
Custom error messages
Two levels:
- Per-field — set
validation.error_messageon the field. Wins for that field's rules. - Per-form — under Settings → Errors, you can override the default messages globally (
This field is required,Must be at least {min} characters, etc.). Useful for non-English forms.
Server-side enforcement
Every submission goes through SchemaValidator on the server before it's persisted. The validator:
- Drops keys that aren't in the schema (honeypot is the obvious one).
- Coerces types (string → number, ISO date → date).
- Applies the rules above.
- Returns 422 with a JSON body of
{ "errors": { "field_name": ["message"] } }if anything fails.
Your fetch caller can show those messages directly, or you can let the hosted page render them.
Legacy forms
If you're using legacy mode (your own HTML pointed at the endpoint), only the spam stack runs. Add required, pattern, minlength, maxlength to your <input> tags directly — that's the only validation layer. We don't validate against a schema because there is no schema.
What's next
- Field types → — what each type accepts
- Custom rules → — block submissions by content patterns
- Files → — upload caps, signed URLs, retention