Emtiaz
ServicesCase StudiesTestimonialsAboutFAQBlogs
Advanced Tracking7 min read

Tracking Contact Form 7 Submissions with GTM

Emtiaz Hossain

Author Name

Emtiaz Hossain

Last Edit : Jul 19, 2026, 11:00:00 AM
Tracking Contact Form 7 Submissions with GTM

TABLE OF CONTENTS▼
Why the Click Trigger Alone Is WrongThe PatternWhat Makes This ReliableWiring It Into GTMKey Takeaways

Contents

0%
Why the Click Trigger Alone Is WrongThe PatternWhat Makes This ReliableWiring It Into GTMKey Takeaways

Contact Form 7 doesn't give you a clean built-in dataLayer event, and its default AJAX submission means a simple click trigger on the submit button fires whether the submission actually succeeded or failed validation. This is the pattern used to track CF7 forms accurately: reading field values directly, then confirming success by watching for CF7's own success message rather than trusting the click event.

Why the Click Trigger Alone Is Wrong

CF7 submits over AJAX by default. The submit button gets clicked whether the form passes validation or not, a required field left empty, an invalid email format, anything that blocks a real submission still lets the click event fire. A GTM tag built on a plain click trigger will report every attempt as a conversion, inflating form-submit numbers with people who never actually got through.

The Pattern

javascript
<script>
(function() {
  var form = document.querySelector('form.wpcf7-form');
  if (form) {
    form.addEventListener('submit', function(e) {
      if (window.hasFormSubmitted) return;
      window.hasFormSubmitted = true;

      var pagePath = window.location.pathname;
      var userFromValue = pagePath === '/' ? 'homepage' : pagePath.replace(/\//g, '');

      function getValue(selector) {
        var element = document.querySelector(selector);
        return element ? element.value : '';
      }

      var first_name = getValue("input[name='first-name']");
      var last_name = getValue("input[name='last-name']");
      var email = getValue("input[name='your-email']");
      var phone = getValue("input[name='phonetext-519']");
      var message = getValue("textarea");

      function checkSuccessMessage() {
        var responseOutput = document.querySelector('#wpcf7-f787-o1 > form > div.wpcf7-response-output');
        if (responseOutput && responseOutput.textContent.trim() === 'Thank you for your message. It has been sent.') {
          window.dataLayer = window.dataLayer || [];
          window.dataLayer.push({
            event: 'cf7_submit_v2',
            user_from: userFromValue,
            formID: userFromValue + '_wpcf7-f787-o1',
            user_data: {
              first_name: first_name,
              last_name: last_name,
              email: email,
              phone: phone,
              message: message
            }
          });
          clearInterval(checkInterval);
        }
      }

      var checkInterval = setInterval(checkSuccessMessage, 500);

      window.addEventListener('beforeunload', function() {
        clearInterval(checkInterval);
      });
    });
  }
})();
</script>

What Makes This Reliable

Field values are captured at submit time, not after. CF7 sometimes clears or resets fields once a submission goes through, so reading first-name, your-email, and the rest inside the submit handler, before CF7's own AJAX response comes back, guarantees the values still reflect what the user actually typed.

Success is confirmed by CF7's own response text, not the submit event. CF7 renders its success or error message into a .wpcf7-response-output element after the AJAX call resolves. Polling for that element's exact success text via setInterval is what separates "the button was clicked" from "the message was actually sent." Adjust the selector (#wpcf7-f787-o1 in this example) and the exact success string to match the specific form being tracked, both are unique per CF7 instance and visible in the page's rendered HTML.

A submission flag prevents duplicate pushes. window.hasFormSubmitted blocks the handler from firing twice if a user double-clicks submit or the event listener somehow attaches more than once, a small guard that avoids inflating the count with duplicate events from the same real submission.

The interval is cleaned up on both success and page exit. clearInterval fires the moment the success message is detected, and again on beforeunload as a safety net, so the polling loop never keeps running in the background after the user has moved on.

Wiring It Into GTM

  1. Add the script as a Custom HTML tag, updating the field selectors and the wpcf7-f787-o1 identifiers to match the actual form (inspect the rendered page to find the real form ID and response-output container ID).
  2. Trigger the tag on the page(s) where the form appears, typically an "All Pages" or DOM Ready trigger scoped to the relevant URL path.
  3. Build a GA4 Event tag listening for cf7_submit_v2, mapping the user_data fields as needed for enhanced conversions or CAPI.
  4. Test in GTM Preview by submitting the actual form with valid data, and separately with an invalid field, to confirm the event only fires on the valid, successful submission.

Key Takeaways

  • ▸Never trust a submit-button click trigger for CF7, AJAX submissions clicked with invalid data still fire the click event
  • ▸Confirm success by polling for CF7's own response-output text, not by assuming the AJAX call succeeded
  • ▸Read field values inside the submit handler before CF7 potentially clears them post-submission
  • ▸Guard against duplicate pushes with a simple flag, and always clean up polling intervals on both success and page exit
#GTM#CustomTracking

Discussed this on LinkedIn

Found this helpful?

Drop a comment on the LinkedIn post – I read every reply and love connecting with tracking folks.

View on LinkedIn

Not Sure If Your Tracking Is Costing You?

Book a free 30-minute call. I will review your current tracking setup, show you where conversions are leaking, and tell you exactly what I would fix.

More Blog Posts

View all
Tracking WPForms Submissions with GTM
Advanced Tracking

Tracking WPForms Submissions with GTM

WPForms has two submission modes, AJAX and non-AJAX, that need completely different GTM tracking patterns. Here's the real code for both, plus the validation logic non-AJAX forms need.

Tracking Ninja Forms Submissions with GTM
Advanced Tracking

Tracking Ninja Forms Submissions with GTM

Ninja Forms fires its own jQuery success event with structured field data attached, making it the simplest of the major form plugins to track accurately in GTM.

The Complete Guide to Form Tracking with Google Tag Manager
Advanced Tracking

The Complete Guide to Form Tracking with Google Tag Manager

A universal dataLayer pattern for tracking any form (name, email, phone, date, country, message) in GTM, the foundation every plugin-specific guide in this series builds on.

Emtiaz
EmailLinkedInPrivacyTerms

© 2026 Emtiaz Hossain