Skip to content

Track Contact Form 7 Submissions

Contact Form 7 dispatches four native DOM events — only one of them means “the email was actually sent.” This recipe picks the right one, wires it to the dataLayer, and sends a GA4 event with the form ID and unit tag so you can disambiguate multiple CF7 forms on the same page.

Valid as of April 2026, Contact Form 7 5.9.x.

Contact Form 7 fires four custom events on document:

EventWhen it firesUse for conversions?
wpcf7submitAny submit attempt, successful or notNo
wpcf7invalidValidation failedNo
wpcf7spamMarked as spam (Akismet/honeypot)No
wpcf7mailfailedValidation passed but mail send failedNo
wpcf7mailsentMail sent successfullyYes

Listening to wpcf7submit is the single most common mistake — it fires on every attempt including validation errors, which will massively inflate your lead count.

CF7 events are plain DOM CustomEvents — no jQuery needed:

dataLayer.push() form_submission

Fires only when CF7's mailer confirms the message was sent.

document.addEventListener('wpcf7mailsent', function (event) {
var contactFormId = event.detail.contactFormId;
var unitTag = event.detail.unitTag;
var $form = event.target;
var formTitle = $form && $form.getAttribute('aria-label')
|| document.querySelector('[id="' + unitTag + '"] .wpcf7-form-title')
&& document.querySelector('[id="' + unitTag + '"] .wpcf7-form-title').textContent.trim()
|| 'cf7_' + contactFormId;
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'form_submission',
form_vendor: 'contact_form_7',
form_id: String(contactFormId),
form_title: formTitle,
form_unit_tag: unitTag
});
});

The unitTag (for example wpcf7-f42-p15-o1) uniquely identifies the specific form instance on the page. If the same CF7 form is embedded twice on one page, contactFormId is identical but unitTag differs.

  1. Create a Custom Event trigger

    • Trigger type: Custom Event
    • Event name: form_submission
    • This fires: Some Custom EventsDLV - form_vendor equals contact_form_7
  2. Create Data Layer Variables

    • DLV - form_vendorform_vendor
    • DLV - form_idform_id
    • DLV - form_titleform_title
    • DLV - form_unit_tagform_unit_tag
  3. Create a GA4 Event Tag

    • Event name: generate_lead
    • Parameters:
      • form_vendor{{DLV - form_vendor}}
      • form_id{{DLV - form_id}}
      • form_title{{DLV - form_title}}
      • form_unit_tag{{DLV - form_unit_tag}}
    • Trigger: the Custom Event trigger above
  4. Test in Preview mode

    Submit a CF7 form with valid data. Confirm the form_submission event appears once per submission in the Summary pane.

Tag Configuration

GA4 - generate_lead (CF7)

Type
Google Analytics: GA4 Event
Trigger
Custom Event - form_submission (contact_form_7)
Variables
DLV - form_vendorDLV - form_idDLV - form_titleDLV - form_unit_tag

If you want to see how often forms fail validation or mail-send, listen to the other events and push a separate event:

['wpcf7invalid', 'wpcf7spam', 'wpcf7mailfailed'].forEach(function (eventName) {
document.addEventListener(eventName, function (event) {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'form_error',
form_vendor: 'contact_form_7',
form_id: String(event.detail.contactFormId),
error_type: eventName.replace('wpcf7', '')
});
});
});

Create a separate GA4 tag form_error with an error_type parameter. Useful for surfacing SMTP misconfiguration — if mailfailed is non-zero, your server is failing to send even though users see success.

The event.detail.inputs array contains every submitted field:

document.addEventListener('wpcf7mailsent', function (event) {
var fields = {};
(event.detail.inputs || []).forEach(function (input) {
fields[input.name] = input.value;
});
// fields['your-email'], fields['your-name'], etc.
});
  1. Open GTM Preview mode on a page containing a CF7 form.
  2. Submit the form with valid data. Confirm form_submission appears in the Summary pane with the correct form_id and form_unit_tag.
  3. Verify Tags Fired lists the GA4 tag.
  4. In GA4 → DebugView, confirm generate_lead arrives within 15 seconds.
  5. Test failure path: submit with an invalid email format. Confirm wpcf7invalid appears in the dataLayer (if you added error tracking) but no form_submission fires.
  6. Test spam path: if you have Akismet enabled, submit known spam content and verify wpcf7spam is dispatched.

Listening to wpcf7submit. This is the number one CF7 tracking bug. It fires on every attempt. Use wpcf7mailsent.

Multiple CF7 forms on one page. contactFormId is shared if it is the same CF7 form embedded twice; only unitTag is unique. Report on both.

Confirmation redirect via on_sent_ok. The deprecated on_sent_ok hook redirected on success and suppressed the DOM event. CF7 removed it in 5.0. If you see legacy code using it, upgrade — the current pattern is wpcf7mailsent plus a redirect inside your listener.

Flamingo plugin. Flamingo stores submissions in the database but does not interfere with wpcf7mailsent. You can safely use both.

Forms loaded via AJAX. If a CF7 form is injected into the page after initial load (for example inside a modal fetched via AJAX), the listener on document still works because it is delegated. No re-binding needed.

event.detail is undefined in old browsers. CF7 uses CustomEvent which IE11 supported only with a polyfill. If you must support IE11 (you probably don’t in 2026), include the CustomEvent polyfill before your listener.