JavaScript events (analytics & GTM)
OctaForms tracks itself. Every form and every part of the callback widget reports what visitors do, and those reports go straight into the dataLayer that Google Tag Manager reads. You do not paste any code. You build one trigger in GTM and you have conversions.
This is the page to hand to whoever sets up your analytics.
Set up a conversion in GTM
Section titled “Set up a conversion in GTM”Three steps, start to finish.
1. Make a trigger. In GTM go to Triggers → New → Custom Event and set the event name to octaforms_form_success. That fires every time someone successfully submits any OctaForms form.
2. Make the variables you want to report on. Variables → New → Data Layer Variable, one for each: octaFormSlug (which form) and octaFormLocation (where it sits on your site).
3. Make the tag. Tags → New → Google Analytics: GA4 Event, pick your trigger from step 1, and name the event something GA4 accepts, for example generate_lead. Add form_slug = {{octaFormSlug}} as a parameter and you can tell your forms apart in reports.
To count only one form, add a condition to the trigger: octaFormSlug equals contact.
What gets sent
Section titled “What gets sent”Every event lands in the dataLayer under a name starting with octaforms_. If you want one tag for everything, trigger on the octaforms_ prefix and set the GA4 event name to {{Event}}.
| dataLayer event | Fires when |
|---|---|
octaforms_form_start |
someone first clicks into a form field |
octaforms_form_submit |
someone sends a form |
octaforms_form_success |
a form goes through |
octaforms_form_error |
a submit is rejected or fails |
octaforms_widget_open |
the callback widget popup opens |
octaforms_tab_switch |
someone switches a tab in the widget |
octaforms_phone_call |
someone taps your phone number |
octaforms_phone_copy |
someone copies your phone number |
octaforms_step_change |
someone moves to another step of a form shown as steps |
octaforms_step_change is new in OctaForms 1.5.0. If you use one trigger for the whole octaforms_ prefix, for example “Event matches RegEx octaforms_.*”, it now lets this event through too, and a GA4 tag named {{Event}} starts sending octaforms_step_change to GA4. If you do not want it there, add the condition “Event does not equal octaforms_step_change” to that trigger.
Every event carries the same set of variables. The ones that do not apply are null, so a value from one event can never leak into the next.
| Variable | Meaning |
|---|---|
octaFormSlug |
which form it was (contact, callback, message) |
octaFormLocation |
where the form sits, read from the context attribute (see below) |
octaFormStatus |
for a failed submit: the HTTP status, for example 400 when a field is invalid, 429 when too many attempts. 0 means the request never reached the server |
octaFormReason |
why a submit never got an answer from the server: network, timeout, token, preflight or error (see below) |
octaFormTab |
which widget tab: phone, callback or message |
octaFormPhone |
your phone number, on the two phone events |
octaFormTrigger |
how a call was started. A tap on the number in the callback widget sends widget |
octaFormSuccess |
whether copying the number worked |
octaFormStep |
the section someone moved to, counting from 1. A step whose fields are all hidden is skipped, but the numbers stay those of the sections, so step 4 is always the fourth section |
octaFormStepTotal |
how many sections the form has, skipped ones included |
octaFormStepTitle |
the title of that step’s section, null when it has none |
octaFormsSchema |
version of this data shape, currently 1. It changes only when a variable is renamed, removed or changes meaning, never when an event or a variable is added |
Name your form placements
Section titled “Name your form placements”By default a form reports its slug as the location, so the same form used in three places looks like one. Give each placement a name with the context attribute:
[octa_form slug="contact" context="Contact page"][octa_form slug="contact" context="Footer"]That name arrives as octaFormLocation. It is also saved with every submission, so what you see in GA4 matches what you see in your submissions list.
Reading the funnel
Section titled “Reading the funnel”The four form events fit together on purpose.
octaforms_form_start fires once per form, the first time someone really interacts with it. octaforms_form_submit fires when they send it. Then exactly one of octaforms_form_success or octaforms_form_error follows. Every submit gets an outcome, so form_submit minus form_success is your real failure count.
A form shown as steps adds octaforms_step_change each time someone moves to another step, forward or back. Count it per octaFormStep and you see how far people get before they give up. It does not fire when the page loads, because octaforms_form_start already marks the first step.
Some things deliberately send nothing:
- If a submit hits a temporary hiccup, OctaForms quietly retries it. You get no error for an attempt that then succeeds, and no second
form_submit. - If someone picks a file that is too big or the wrong type, the form says so without contacting the server, so there is no event.
One thing to know about spam
Section titled “One thing to know about spam”octaforms_form_success also counts submissions that were caught as spam and quarantined.
This is deliberate. A spam bot must never learn it was caught, so it gets the same response a real visitor gets, which means your browser cannot tell them apart either. Bots rarely run JavaScript, so in practice this is a small number, but if your conversion count sits slightly above the leads in your inbox, this is why. Your submissions list always shows the truth.
Turning it off
Section titled “Turning it off”The push is on by default and needs no setting. It writes nothing, sends nothing and adds no recipient. It just puts a few entries in a list on the page, and nothing goes anywhere until you build a trigger in GTM. So the decision is already yours, it just lives in GTM.
It sends no personal data either. There is no name, e-mail, phone number or message text, and no submission ID. The only phone number in there is your own.
Note that this does not handle consent for you. GTM fires tags according to how you configured it, so if you need consent before analytics runs, set that up with your consent tool as usual.
Developers can switch the push off entirely:
add_filter( 'octa_forms_gtm_push_enabled', '__return_false' );If your container uses a renamed dataLayer, tell OctaForms:
add_filter( 'octa_forms_gtm_data_layer', function () { return 'myDataLayer';} );For developers: the raw events
Section titled “For developers: the raw events”Under the hood, everything above is built on browser events named octaforms:* that fire on the form and bubble up to document. The GTM push just listens for them and translates. If you write your own JavaScript, you can listen too.
| Event | detail |
|---|---|
octaforms:form-start |
slug, context, location |
octaforms:form-submit |
slug, context, location |
octaforms:form-success |
slug, context, location |
octaforms:form-error |
plus status, and reason when status is 0 |
octaforms:widget-open |
location, mode |
octaforms:tab-switch |
location, tab |
octaforms:phone-call |
location, phone, trigger (widget for the callback widget) |
octaforms:phone-copy |
location, phone, success |
octaforms:step-change |
slug, context, location, step, total, title |
document.addEventListener( 'octaforms:form-success', ( e ) => { console.log( e.detail.slug, e.detail.location );} );This contract is shared by other OctaForms products, so a GTM container configured once works across them. The event names, keys and rules are identical everywhere. Only the possible status and reason values differ per product, because they describe that product’s own transport. For this plugin, a rejected submit carries the status the server actually returned (400 for invalid fields, 429 when rate-limited) and reason is one of network, timeout, token, preflight or error. One rule holds in every product: if nothing was sent over the network, no event fires.
timeout comes only after a second try. A submit with no answer within 25 seconds is sent once more, silently and with the same token, so a lead the server already saved is not saved twice. Only when that try runs out of time too does the visitor see a message and GTM a form-error.
error means something in the page’s code failed during the submit, for example a script of the theme throwing an error. The visitor sees a message and the form can be sent again.
preflight is the narrow case. A file rejected by the browser-side check normally emits nothing at all, because no round-trip happened. The one exception is a rejection during an automatic retry, where a form-submit has already fired and would otherwise stay open forever, so the funnel is closed with this reason.
Two more events fire on the form element only and do not bubble. They carry the full server response, for when you need the payload rather than a measurement:
form.addEventListener( 'octaforms:success', ( e ) => { // e.detail is the full submission result from the server} );form.addEventListener( 'octaforms:error', ( e ) => { // e.detail is { status, errors }} );These two only fire when the server actually answered. A submit killed by a dead connection reports through octaforms:form-error with status: 0 instead, because there is no server response to hand you. See The JS helper.
Why there is no submission ID
Section titled “Why there is no submission ID”The events carry no submission ID, and that is on purpose.
A quarantined spam submission gets a randomly numbered response, drawn from the same range as real submissions, precisely so a bot cannot tell it was caught. So an ID in your analytics would be part real database key and part noise, with no way to tell which is which. Matching a conversion to the wrong lead is worse than not matching at all.
If you need to reconcile GA4 with your database, add your own key through the octa_forms_response filter, which only runs for genuine submissions, and read it from the raw octaforms:success event. Be aware of the trade-off: it makes real and quarantined responses differ, which tells a determined spammer that your honeypot caught them.
Already pasted a bridge snippet?
Section titled “Already pasted a bridge snippet?”Earlier versions of this page asked you to paste a script that forwarded these events to the dataLayer. You no longer need it, and you should remove it. OctaForms sets window.octaFormsGtmBridge when its own push installs, so if you keep your own listener, check that flag first or every conversion will be counted twice.