Tracking ProForms submission as a conversion with GA / GTM

Hi experts,

I’m trying to track ProForms submission as a GA4 event with GTM.

When with JetForm in crocoblocks, I always used to add custom code like below to generate custom event on listening a successful form submission:

https://codeshare.io/5vAeAl

However, with ProForms, I have no idea how to listen the submissin in JavaScript.

Have anyone out there tried conversion tracking of ProForms?

Will appreciate any piece of advice!

Regrds,

Mayumi

1 Like

Hi everyone,

I’ve successfully implemented form submission tracking for ProForms using a MutationObserver approach. Since ProForms doesn’t currently fire native JS events, I’m monitoring the DOM changes that occur during form submission.

How it works:

The solution observes changes to each form element and detects:

  1. When the submit button gets the sending class (submission starts)
  2. When a success message (div.message.success) appears (submission complete)
  3. Form field values are captured at submit time before the form gets cleared

The tracking script:


(function() {
  var formSelector = '.brxe-brf-pro-forms';
  var formElements = document.querySelectorAll(formSelector);
  
  if (formElements.length === 0) return;

  var blacklist = ['cf-turnstile-response', 'action', 'loopId', 'postId', 'formId', 'recaptchaToken', 'nonce', 'referrer', 'urlParams', '_wpnonce', '_wp_http_referer'];
  
  formElements.forEach(function(formElement) {
    var formState = {
      isSubmitting: false,
      hasSubmitted: false,
      elementId: formElement.getAttribute('data-element-id') || 'unknown',
      formData: null,
      capturedAtSubmit: false
    };

    function collectFormData() {
      var formDataRaw = new FormData(formElement);
      var formDataObj = {};
      var entries = formDataRaw.entries();
      var entry = entries.next();
      
      while (!entry.done) {
        var key = entry.value[0];
        var value = entry.value[1];
        
        if (blacklist.indexOf(key) === -1) {
          if (formDataObj[key]) {
            if (!Array.isArray(formDataObj[key])) {
              formDataObj[key] = [formDataObj[key]];
            }
            formDataObj[key].push(value);
          } else {
            formDataObj[key] = value;
          }
        }
        entry = entries.next();
      }
      return formDataObj;
    }

    function trackFormSuccess() {
      if (formState.hasSubmitted) return;
      formState.hasSubmitted = true;

      if (typeof dataLayer !== 'undefined') {
        dataLayer.push({
          event: 'bricks-form-success',
          formId: formState.elementId,
          formData: formState.formData,
          'gtm.elementId': formState.elementId
        });
      }
    }

    var observerConfig = {
      attributes: true,
      attributeFilter: ['class'],
      childList: true,
      subtree: false
    };

    var observer = new MutationObserver(function(mutations) {
      mutations.forEach(function(mutation) {
        if (mutation.type === 'attributes' && mutation.attributeName === 'class' && mutation.target.tagName === 'BUTTON') {
          if (mutation.target.classList.contains('sending')) {
            formState.isSubmitting = true;
          } else if (formState.isSubmitting) {
            formState.isSubmitting = false;
          }
        }

        if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
          mutation.addedNodes.forEach(function(node) {
            if (node.nodeType === 1) {
              if (node.classList && node.classList.contains('message') && node.classList.contains('success')) {
                trackFormSuccess();
              }
              
              try {
                var successMsg = node.querySelector('.message.success');
                if (successMsg) {
                  trackFormSuccess();
                }
              } catch(e) {}
            }
          });
        }

        if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
          mutation.addedNodes.forEach(function(node) {
            if (node.nodeType === 1 && node.classList) {
              if (node.classList.contains('message') && node.classList.contains('error')) {
                if (typeof dataLayer !== 'undefined') {
                  dataLayer.push({
                    event: 'bricks-form-error',
                    formId: formState.elementId,
                    formData: collectFormData(),
                    errorMessage: node.textContent.trim(),
                    'gtm.elementId': formState.elementId
                  });
                }
                formState.isSubmitting = false;
              }
            }
          });
        }
      });
    });

    observer.observe(formElement, observerConfig);

    var submitButton = formElement.querySelector('button[type="submit"]');
    if (submitButton) {
      submitButton.addEventListener('click', function(e) {
        formState.formData = collectFormData();
        formState.capturedAtSubmit = true;
      }, true);
    }
  });
})();

Features:

  • Tracks multiple ProForms on the same page independently
  • Works with Google Tag Manager / GA4
  • Captures data before form clears
  • Automatically filters technical fields (nonce, recaptcha tokens, etc.)
  • Each form has its own tracking state
  • ES5 compatible for older browsers
  • Includes error tracking

GTM Setup:

Add this as a Custom HTML tag and create a trigger for the event bricks-form-success. The dataLayer will include:

  • formId (from data-element-id)
  • formData (all captured fields)
  • gtm.elementId

Error Tracking:

The script also tracks form errors with the event bricks-form-error for conversion debugging.

Note:

Each form must have a unique data-element-id attribute to be tracked separately.


Hope this helps others struggling with ProForms conversion tracking! Would be great if BricksForge added native JS event support in the future though. :rocket: