Custom Form Submit
Custom Form Submit
This cHTML tag creates a custom form submit event to help with deduplication issues. Use this as your Form Submit Event Trigger.
Detects standard HTML forms (full-page navigation, or AJAX success blocks from
Webflow, Contact Form 7, Gravity Forms, WPForms, Elementor, and Formidable) as
well as Framer's native forms via Framer's framer:formsubmit event.
Framer note: Framer only emits framer:formsubmit for forms that have a
Tracking ID set in the form's properties panel — set one on each form you want
tracked, or the tag will receive nothing.
The code
custom-form-submit.html
html
<script>
(function () {
'use strict';
if (window.__mtkForms) { return; } // don't double-bind
window.__mtkForms = true;
window.dataLayer = window.dataLayer || [];
// Success blocks: Webflow, CF7, Gravity, WPForms, Elementor, Formidable
var SUCCESS = '.w-form-done, .wpcf7-mail-sent-ok, .gform_confirmation_message, ' +
'.wpforms-confirmation-container, .elementor-message-success, .frm_message';
// Forms that navigate but aren't conversions - these would otherwise false-positive
var IGNORE = 'form[role="search"], form[action*="search"], form[data-mtk-ignore]';
// True once any success block is actually rendered on screen
function shown() {
return [].some.call(document.querySelectorAll(SUCCESS), function (n) { return n.offsetHeight; });
}
document.addEventListener('submit', function (e) {
var form = e.target;
if (form.tagName !== 'FORM' || form.matches(IGNORE)) { return; }
var timer, fired = false;
function push(method) {
if (fired) { return; }
fired = true;
window.clearInterval(timer);
window.dataLayer.push({
event: 'mtk_form_submit',
form_id: form.id || '',
form_name: form.getAttribute('data-name') || form.name || '',
detection_method: method,
event_id: 'mtk-' + new Date().getTime() // pass to Meta CAPI as eventID
});
}
// Nothing called preventDefault, so the browser is navigating - that IS the success
window.setTimeout(function () { if (!e.defaultPrevented) { push('navigation'); } }, 0);
// AJAX form: poll for a success block for 15s (60 x 250ms), then give up
var tries = 0;
timer = window.setInterval(function () {
if (fired || ++tries > 60) { window.clearInterval(timer); return; }
if (shown()) { push('success_block'); }
}, 250);
}, true); // capture, so stopPropagation in page code can't hide the event
// Framer: forms are React components - they preventDefault and submit via
// fetch, and the success state has no stable class, so neither path above
// ever sees them. Framer instead dispatches `framer:formsubmit` on window,
// but ONLY for forms with a Tracking ID set in the form's properties panel.
window.addEventListener('framer:formsubmit', function (e) {
var d = (e && e.detail) || {};
window.dataLayer.push({
event: 'mtk_form_submit',
form_id: d.trackingId || d.id || '',
form_name: d.name || d.trackingId || '',
detection_method: 'framer_event',
event_id: 'mtk-' + new Date().getTime() // pass to Meta CAPI as eventID
});
});
})();
</script>