Field types
Each entry in the config’s fields array describes one input. Every field needs a name and a type. Most have a label, and many carry validation rules.
{ "name": "message", "type": "textarea", "label": "Your message", "rules": [{ "r": "required" }] }The types
Section titled “The types”There are eleven field types.
| Type | Renders as | Value sent |
|---|---|---|
text |
Single-line text input | string |
textarea |
Multi-line text box | string |
email |
E-mail input with an e-mail keyboard and autofill | string |
tel |
Phone input with a numeric keypad and autofill | string |
number |
Text input with a number keyboard | string |
select |
Dropdown, needs an options list |
string |
radio |
One choice from visible options, needs an options list |
string |
checkbox |
Single checkbox | boolean (true or false) |
checkboxes |
Several checkboxes, any number of choices, needs an options list |
list of strings |
date |
Date value in YYYY-MM-DD |
string |
file |
File upload | sent separately, see below |
Common options
Section titled “Common options”Every field supports these:
name(required) — the key used in submissions, emails and webhooks. Must be unique within the form.type(required) — one of the types above.label— the human-readable label. Also used as the column heading in the notification email. On a text input, leave it empty to show only the field with its placeholder.placeholder— on a text input (text,textarea,email,tel,number), the grey hint text shown inside the empty field.default— the value the field starts with. See Starting values.showIf— show the field only after a certain answer in a field above it. See Fields that appear after an answer.hideLabel— set totrueto hide the label on the form, for example on a group of checkboxes whose section title already asks the question. Screen readers still read it, and emails and the submissions screen still use it. Not used on a singlecheckbox, whose text is its consent statement.description— on acheckbox, a longer consent statement shown next to the box, with optional link. Seecheckbox.consent— on acheckbox, set totrueto keep the answer as proof of consent. Seecheckbox.rules— an array of validation rules. See Validation rules.
A field with the required rule shows a small star after its label. Screen readers do not read the star out, because the field is already announced as required.
Text inputs: text, textarea, email, tel
Section titled “Text inputs: text, textarea, email, tel”These all send a plain string. They also accept a placeholder, the grey hint text shown inside the empty field:
{ "name": "company", "type": "text", "label": "Company", "placeholder": "Acme Inc." }Leave the label empty to show just the field with its placeholder, with no label above it. The label is still kept in the markup for screen readers, so the field stays accessible. This applies only to these text inputs. A select, checkbox, date or file always keeps its label.
{ "name": "company", "type": "text", "placeholder": "Company" }The email type renders as <input type="email" inputmode="email" autocomplete="email">, so phones show a keyboard with @ and . ready, and browsers can autofill a saved address. It does not check the value on its own. Add the email rule when you want the address validated:
{ "name": "email", "type": "email", "label": "E-mail", "rules": [{ "r": "required" }, { "r": "email" }] }The tel type renders as <input type="tel" inputmode="tel" autocomplete="tel">, so phones show a numeric keypad and browsers offer to autofill the number. Add the loose international tel rule (6 to 15 digits) to check it. For a Poland-only site, use the stricter telPL rule instead. See Validation rules.
The number type renders as a text input with inputmode="decimal", so phones show a number keyboard. It is deliberately not type="number", which refuses a decimal comma in many languages and changes its value when someone scrolls over it. Add the numeric rule to accept any number, or integer for whole numbers only. A decimal comma is accepted: 12,5 is saved as 12.5. A comma followed by exactly three digits, as in 1,000, reads as a thousand in English, so it is left as typed and the numeric rule asks for a plain number such as 1000. With numeric or integer in place, min:N and max:N set the smallest and largest number allowed, for example min:18 on an age field. See Validation rules.
{ "name": "income", "type": "number", "label": "Income last year", "rules": [{ "r": "numeric" }] }A textarea gets special treatment in the notification email: it renders as a block below the table of values, with the label on its own line and the text underneath, rather than being squeezed into a table cell.
select
Section titled “select”A dropdown. It must define a non-empty options array, and the submitted value has to be one of them (checked on the server, so it cannot be bypassed):
{ "name": "topic", "type": "select", "label": "What is this about?", "options": ["Sales", "Support", "Something else"]}An option can also be an object with a separate value and label, for example { "value": "pro", "label": "Pro package with installation" }. The visitor sees the label. Your email, the submission screen and the CSV export show the label too, so you read what the visitor picked. Webhooks, the thank-you address, default and showIf use the value, which stays the same when you reword the label. If you later remove an option or leave its label empty, older submissions show the stored value instead.
The same goes for radio and checkboxes. Each option needs its own value. An option also cannot contain a double space, a line break or a < sign. Answers are cleaned of those before they are checked, so such an option would never match and nobody who picks it could send the form. The form refuses to save in both cases and points to the option.
One choice from options that are all visible at once. It suits short lists, such as a Yes or No question, where a dropdown would only hide the answers behind an extra click. Like a select, it needs a non-empty options list and the answer must be one of them:
{ "name": "business", "type": "radio", "label": "Do you run a business?", "options": ["Yes", "No"], "default": "No"}The question becomes the group’s title and each option its own clickable line. An unanswered group sends nothing, so add the required rule when an answer is needed.
checkboxes
Section titled “checkboxes”Several options where the visitor can tick any number of them, for example “which documents do you have”. The answer is a list of the ticked options, in the order you declared them. Your email shows that list on one line, for example “Documents: PIT-11, PIT-37”, instead of a separate Yes or No line for every option:
{ "name": "documents", "type": "checkboxes", "label": "Which documents do you have?", "options": ["PIT-11", "PIT-37", "IFT-1"], "rules": [{ "r": "required" }, { "r": "maxChecked:2" }]}requiredmeans at least one box must be ticked.minChecked:NandmaxChecked:Nset how many boxes may be ticked.- Other rules do not apply to a group, and the form editor warns you if you add one.
This is the one field type whose value is a list. See How each field type is serialized if you send forms with your own JavaScript. Not to be confused with checkbox, a single box: if you give a checkbox a list of options, the editor warns you that you probably meant checkboxes.
checkbox
Section titled “checkbox”A single checkbox, typically for consent. Its value is a real boolean. An unchecked box is sent explicitly as false, not left out.
To make a checkbox mandatory (the usual case for a consent box), give it the required rule. OctaForms understands that “required” on a checkbox means “must be ticked”:
{ "name": "consent", "type": "checkbox", "label": "I agree to the privacy policy", "rules": [{ "r": "required" }], "consent": true }Consent text with a link
Section titled “Consent text with a link”The short label is fine for a simple tick. When you need a full consent statement, and especially a link to your privacy policy, put the wording in description. It replaces the label as the text shown next to the box, so the checkbox sits at the top-left and the sentence wraps beside it.
To add a link, wrap the link text in square brackets and put the address in round brackets right after it, like [Privacy policy](https://example.com/privacy):
{ "name": "consent", "type": "checkbox", "label": "Consent", "description": "I agree to the processing of my personal data in line with the [Privacy policy](https://example.com/privacy), so that this contact form can send my message.", "consent": true, "rules": [{ "r": "required" }]}The link opens in a new tab so the visitor does not lose what they typed. Everything else in the text is shown as plain text, so pasting HTML tags is safe. They appear literally rather than being rendered. In the form editor this is the Consent text box under the checkbox field.
Keeping proof of a consent
Section titled “Keeping proof of a consent”Add "consent": true to keep the answer as proof. OctaForms then stores the exact wording the visitor saw, their answer, the time and the version of the form, as a consent snapshot on the submission. The wording is the description if you set one, otherwise the label, then the placeholder, then the field name. Such a box never starts ticked, because a pre-ticked box is not valid consent: a default of true is refused when you save, and a tick set by a script on the page without the visitor’s click is sent as not ticked. A theme that draws its own checkbox and passes the visitor’s click on to the real one still works. See Privacy & GDPR. In the form editor this is the Record consent switch under the checkbox field.
The flag is separate from required on purpose, because the two answer different questions:
- Required but not a consent. “I have checked my details” must be ticked, but proves nothing worth keeping. Leave the flag off.
- A consent that is optional. A marketing opt-in is the one you most need to prove, and the visitor is free to say no. Turn the flag on and skip the
requiredrule. A refusal is recorded too.
If you have a required checkbox and do not say either way, the form editor warns you once. Set the switch on or off to answer it.
Starting values (default)
Section titled “Starting values (default)”Any field except a file upload can start with a value already filled in. The visitor can still change it.
| Type | default |
|---|---|
text inputs, date |
the text to fill in |
select, radio |
one of the options |
checkboxes |
a list of the options to tick |
checkbox |
true to start ticked |
{ "name": "business", "type": "radio", "options": ["Yes", "No"], "default": "Yes" }A default that is not one of the field’s options is refused when you save, because the form would otherwise start empty while you think it does not.
Fields that appear after an answer (showIf)
Section titled “Fields that appear after an answer (showIf)”A field can stay hidden until the visitor gives a certain answer in a field above it. The classic case is a Yes or No question with a follow-up that only makes sense after Yes:
{ "name": "business", "type": "radio", "label": "Do you run a business?", "options": ["Yes", "No"] },{ "name": "tax_number", "type": "text", "label": "Tax number", "rules": [{ "r": "required" }], "showIf": { "field": "business", "equals": "Yes" }}There are two kinds of condition:
equalsshows the field when the other field’s answer is exactly that value. On acheckboxesgroup it means “this option is ticked”.checkedfollows a singlecheckbox:trueshows the field when the box is ticked,falsewhen it is not.
While a field is hidden, its rules do not apply, so required above only asks for a tax number after Yes. Anything typed into it before the visitor changed their mind is dropped, so it never reaches your email, your submissions or a webhook. The server checks the conditions itself and does not rely on what the browser shows.
A condition can only point at a field above it. A field that depends on a hidden field is hidden too. In the form editor this is the Show only when setting on each field.
A calendar date, sent as an ISO string like 2026-07-08. The type alone does not check it. Add the date rule and the server checks both the format and that the date is real, so 2026-02-30 is rejected. How the date picker looks and how the value is displayed is up to your theme.
A file upload. Files are handled differently from every other type, for good reasons:
- The uploaded file is not sent inside the
fieldsobject. It travels as a separate part of the request. Your integration needs to send it the right way. See Your own JavaScript for the transport details, or just use the JS helper, which handles it for you. - The file’s type is detected from its actual contents on the server, never from its name or the browser’s claim. Executable files are always rejected.
- One file per field, for now.
You constrain uploads with rules specific to file fields: mimes (allowed types), maxSize (in megabytes), and maxFiles. See Validation rules.
Out of the box a form accepts PDFs, images (JPEG, PNG, GIF, WebP), Office and OpenDocument files (doc, docx, xls, xlsx, odt, ods), plain text and CSV. Plain .zip archives are not on that list: nobody can tell what is inside an archive, and the file goes on to your inbox as an email attachment. If you really do collect archives, add the type yourself with the octa_forms_allowed_file_types filter.
Your mimes and maxSize rules also reach the visitor’s browser as hints, so the JS helper can flag a too-large or wrong-type file before the upload starts. These are hints only. The server always re-checks the size and the real contents, so the rules are what actually enforce your limits.
{ "name": "cv", "type": "file", "label": "Your CV", "rules": [{ "r": "required" }, { "r": "mimes:pdf,doc,docx" }, { "r": "maxSize:5" }]}Because the type is read from the file’s real contents, a file has to look like what it claims to be. A PDF with junk before its %PDF header, or a file with no extension at all, sniffs as a generic “octet-stream” and is rejected. This is deliberate: it is what stops a renamed executable from slipping through. If you have a genuine case that needs a looser check, you can widen what counts as an acceptable content type for an extension with the octa_forms_attachment_allowed filter:
// Accept a specific extension even when the server sniffs it as a generic type.add_filter( 'octa_forms_attachment_allowed', function ( $allowed, $ext, $finfo_mime ) { if ( 'pdf' === $ext && 'application/octet-stream' === $finfo_mime ) { return true; } return $allowed;}, 10, 3 );Where uploaded files go and how they reach you is covered in Emails (the file is attached to your notification) and Webhooks (webhooks get the file’s name, type and size, never the bytes).
Honeypot fields
Section titled “Honeypot fields”Any field can be turned into a spam trap by adding "honeypot": true. A honeypot is a hidden field that real people never fill in but bots often do. It is not sent to visitors as a normal field, is never stored, and takes no rules.
The one thing that matters is its name: pick something browsers will never try to autofill. Names like company, website, phone, email or name are risky, because a browser’s autofill can fill them for a real visitor and land their submission in the spam quarantine. The config validator warns you if you choose a risky name.
{ "name": "contact_reason_extra", "type": "text", "honeypot": true }How to render a honeypot correctly, and what happens when one is tripped, is covered in Anti-spam.