Skip to content

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.

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 name of each input must match a field name in the form’s config.
  • The helper sends one value per input name, with one exception. For a checkboxes field, name every box fieldname[] and give each its option as value. The helper then sends the ticked values as a list under fieldname, and an empty list when none is ticked. Any other group of checkboxes sharing one name collapses to the last box, and a <select multiple> sends only the first selected option.
  • A radio field is a group of <input type="radio"> sharing the field’s name. 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 with fetch and sets the correct content type itself.
  • accept is only a hint to the browser. The server checks the real file type by inspecting the contents.
  • The helper also reads accept and, when the field has a maxSize rule, a data-octa-forms-max-size attribute (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.
  1. Fetches a security token on the visitor’s first interaction with the form, so pages that are only viewed cost nothing.
  2. Sends the submission to the REST API in the correct format, including any uploaded file.
  3. Handles the token’s retry cases automatically (for example, a submit that came in suspiciously fast).
  4. On success, shows the thank-you message or follows the configured redirect.
  5. 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.

The helper renders validation errors as:

  • span.octa-forms-error next to the field that failed. For a radio or checkbox group it goes at the end of the group’s fieldset[data-octa-forms-field], under the last option.
  • div.octa-forms-error--form at 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, and data-octa-forms-show-if='{"field":"business","equals":"Yes"}' on the wrapper of a field with a showIf. The helper hides that wrapper with hidden and 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-section on each of the N section wrappers, and a step bar with .octa-forms__step-progress, .octa-forms__step-back and .octa-forms__step-next inside a .octa-forms__steps element. The helper shows one section at a time, checks the current step on the server when Next is pressed, and emits octaforms: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.

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.

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).

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 null

attach() 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.

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.
});

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 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

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.