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:
- When the submit button gets the
sending class (submission starts)
- When a success message (
div.message.success) appears (submission complete)
- 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. 