The JS helper
You write the form’s HTML. The helper takes care of everything else: fetching the security token, sending the submission, retrying when it should, and showing validation errors next to the right fields. It is part of the frontend bundle that always loads, so there is nothing extra to enqueue.
Mark your form
Section titled “Mark your form”Add data-octa-forms="slug" to your <form>, and the helper binds to it automatically:
<form data-octa-forms="contact" enctype="multipart/form-data"> <input type="text" name="name" /> <input type="text" name="email" /> <label><input type="checkbox" name="consent" /> I agree to be contacted</label> <input type="file" name="cv" accept=".pdf,application/msword" /> <!-- honeypot field, see Anti-spam --> <button type="submit">Send</button></form>A few notes on that markup:
- The
nameof each input must match a fieldnamein the form’s config. - The helper sends one value per input name, with one exception. For a
checkboxesfield, name every boxfieldname[]and give each its option asvalue. The helper then sends the ticked values as a list underfieldname, and an empty list when none is ticked. Any other group of checkboxes sharing onenamecollapses to the last box, and a<select multiple>sends only the first selected option. - A
radiofield is a group of<input type="radio">sharing the field’sname. The helper sends the checked value, or nothing when none is checked. enctype="multipart/form-data"only matters for a no-JavaScript fallback. The helper submits withfetchand sets the correct content type itself.acceptis only a hint to the browser. The server checks the real file type by inspecting the contents.- The helper also reads
acceptand, when the field has amaxSizerule, adata-octa-forms-max-sizeattribute (in megabytes). It uses them for a quick check before the upload starts. Both are hints only. The server always re-checks. If you write your own markup, add these attributes to get the same early feedback; leave them off and the check is simply skipped.
What the helper does on submit
Section titled “What the helper does on submit”- Fetches a security token on the visitor’s first interaction with the form, so pages that are only viewed cost nothing.
- Sends the submission to the REST API in the correct format, including any uploaded file.
- Handles the token’s retry cases automatically (for example, a submit that came in suspiciously fast).
- On success, shows the thank-you message or follows the configured redirect.
- On failure, places each validation message next to its field.
You never have to touch the token or the request format. If you do want that level of control, see Your own JavaScript.
Where errors appear
Section titled “Where errors appear”The helper renders validation errors as:
span.octa-forms-errornext to the field that failed. For a radio or checkbox group it goes at the end of the group’sfieldset[data-octa-forms-field], under the last option.div.octa-forms-error--format the top of the form, for errors that are not tied to one field.
Styling those is up to your theme.
Conditional fields and steps in your own markup
Section titled “Conditional fields and steps in your own markup”The helper drives two features from data attributes, so your own markup gets them too:
- Conditional fields. Put
data-octa-forms-field="fieldname"on each field’s wrapper, anddata-octa-forms-show-if='{"field":"business","equals":"Yes"}'on the wrapper of a field with ashowIf. The helper hides that wrapper withhiddenand disables its controls while the condition is not met, so its value is not sent. The server applies the same condition from the config anyway, so the attribute only drives what the visitor sees. - Steps. Put
data-octa-forms-steps="N"on the form,data-octa-forms-sectionon each of the N section wrappers, and a step bar with.octa-forms__step-progress,.octa-forms__step-backand.octa-forms__step-nextinside a.octa-forms__stepselement. The helper shows one section at a time, checks the current step on the server when Next is pressed, and emitsoctaforms:step-change. If the final submit is rejected, it opens the step that holds the first rejected field. The easiest way to get the markup right is to copy what the[octa_form]shortcode prints.
File uploads
Section titled “File uploads”When a form has a file field, the helper does two extra things for the visitor.
First, it checks the chosen file before sending. If the file is larger than the field allows, or is a type the form does not accept, the helper shows the message next to the field and does not start the upload. This saves the visitor from waiting for a large file to upload only to have it rejected. It is a convenience only. The server still measures the size and inspects the real contents on every submission, so this can never let a bad file through and never blocks a file the server would have accepted.
Second, it shows an upload progress bar above the submit button while the file is on its way. The bar fills as the bytes are sent, then switches to a moving “working” state while the server processes the submission. It is a native <progress class="octa-forms__progress"> element, so screen readers announce it and your theme can restyle it. Visitors who prefer reduced motion get a still bar instead of the moving one.
A slow connection uploading a large file is never cut off part way: the upload only times out if it stalls, not because it is taking a while.
Events you can listen for
Section titled “Events you can listen for”On success or failure the form fires an event on the form element, carrying the full payload for custom behaviour:
form.addEventListener('octaforms:success', (e) => { // e.detail carries the submission result});form.addEventListener('octaforms:error', (e) => { // e.detail carries the error});For analytics or Google Tag Manager, listen instead for the neutral octaforms:form-success / octaforms:form-error / octaforms:form-start events on document. They bubble, fire for every form, and are the ones to track. See JavaScript events (analytics & GTM).
Forms added after the page loads
Section titled “Forms added after the page loads”The helper scans for form[data-octa-forms] once, when the page’s DOM is ready. A form you insert later, for example one revealed in a modal or cloned from a template, will not be picked up on its own. Bind it by hand:
const form = modal.querySelector('form[data-octa-forms="contact"]');window.octaForms.attach(form); // safe to call twice; returns the instance or nullattach() is available as soon as the bundle loads. It is idempotent, guarded by a data-octa-forms-bound attribute, and only binds elements that carry data-octa-forms. This is exactly how the callback widget works internally.
Reacting to forms as they bind
Section titled “Reacting to forms as they bind”window.octaForms.onAttach(fn) registers a listener called with each <form> element as the helper binds it, both during the initial scan and for forms attached later, such as the callback widget’s cloned forms or your own attach() calls. Use it when a separate script must enhance every form. A captcha widget is the typical case: render into each form, write the token into a hidden input, and the helper submits it with the rest of the fields (see Anti-spam). Listeners are isolated in try/catch, so an exception in one never stops a form from binding.
window.octaForms.onAttach((form) => { // Runs once per bound form.});Turning autobind off
Section titled “Turning autobind off”The automatic scan can be switched off with the octa_forms_helper_autobind filter (there is no admin setting for it). When off, the helper skips the initial scan but window.octaForms.attach() still works, so you bind forms yourself. This is for cases where all your forms are inserted dynamically. A shortcode on the page forces the scan back on for its own form.
To remove the plugin’s JavaScript entirely instead, see Frontend assets.
The markup classes
Section titled “The markup classes”The forms rendered by the shortcode and the callback widget use the same stable classes, all in the octa-forms namespace, so your theme can style them:
| Selector | Element |
|---|---|
form.octa-forms |
The form container (also carries data-octa-forms="slug") |
.octa-forms__field |
A field wrapper (plus a type modifier, e.g. .octa-forms__field--checkbox, and --required). It carries data-octa-forms-field="name" |
.octa-forms__required |
The star after a required field’s label |
.octa-forms__options / .octa-forms__option |
The options of a radio or checkbox group, and one option |
.octa-forms__section |
A section card, with __section-title, __section-text, __section-fields and __section-note inside |
.octa-forms__steps |
The step bar, with __step-progress, __step-back and __step-next |
.octa-forms__actions / .octa-forms__submit |
The actions row and the submit button |
.octa-forms__thank-you |
The thank-you screen shown after a successful submit |
.octa-forms__noscript |
The message shown when JavaScript is off |
.octa-forms-error / .octa-forms-error--form |
A field error / a form-level error |
Styling the form
Section titled “Styling the form”The bundled stylesheet gives the form just enough structure to look tidy out of the box, and it is built to get out of your way. Every default rule is wrapped in :where(), so it carries zero specificity and any selector you write beats it. For the most common changes you set a few --octa-forms-* variables on .octa-forms instead of writing override rules.
The full guide, including the variable reference, the checkbox gotcha and how to retint the callback widget, is on its own page: Styling and theming.