How to track Contact Form 7 submissions
GTM4WP turns every Contact Form 7 submission into a data layer event. Each event carries the form ID, the human readable form name and, if you allow it, the submitted field values, so you can build conversion triggers in Google Tag Manager without writing any JavaScript yourself.
This page describes GTM4WP 2.0.0 and newer. Where the legacy 1.x versions of the plugin behaved differently, the older behavior is marked with a bold GTM4WP 1.x: lead-in.
Before you start
- The Contact Form 7 plugin has to be active. GTM4WP listens to the events that Contact Form 7 itself fires in the browser, so without it the integration does nothing.
- Your Google Tag Manager container ID has to be set in GTM4WP and the container code has to be present on the pages that hold your forms.
- Contact Form 7 sends the form over AJAX, so the browser stays on the same page after a submission. There is no thank you page for a Page View trigger to fire on, which is why this integration exists.
Turning the integration on
Go to Settings → Google Tag Manager in your WordPress admin and open the Contact Form 7 panel. It holds three options.
- Contact Form 7 switches the whole integration on. It is off by default, and the two options below it only matter once it is on.
- Submitted field values in the data layer decides how much of what the visitor typed ends up in the data layer. It has a section of its own below.
- Also push GA4 recommended events adds the Google Analytics 4 form events next to the GTM4WP ones. It is off by default and also has its own section below.
Save the settings and load a page that contains one of your forms. The tracker script is added to every page of the site while the integration is on, not only to the pages that hold a form.
GTM4WP 1.x: the single Contact Form 7 checkbox lived on the Integration tab, and the other two options did not exist.
The data layer events
Contact Form 7 fires a DOM event for each possible outcome of a submission. GTM4WP listens to all eight of them and pushes a matching data layer event.
| Data layer event | Contact Form 7 event | When it fires |
|---|---|---|
gtm4wp.contactForm7BeforeSubmit | wpcf7beforesubmit | Before the form is sent to the server, so before the outcome is known |
gtm4wp.contactForm7InvalidInput | wpcf7invalid | The server rejected the submission because at least one field failed validation |
gtm4wp.contactForm7Unaccepted | wpcf7unaccepted | An acceptance field, typically the terms checkbox, was not ticked |
gtm4wp.contactForm7SpamDetected | wpcf7spam | Contact Form 7 classified the submission as spam |
gtm4wp.contactForm7Aborted | wpcf7aborted | The submission was stopped before the mail was sent, usually by another plugin |
gtm4wp.contactForm7MailSent | wpcf7mailsent | The mail was sent successfully |
gtm4wp.contactForm7MailFailed | wpcf7mailfailed | The submission was accepted but the mail could not be sent |
gtm4wp.contactForm7Submitted | wpcf7submit | Every submission, whatever the outcome, right after the matching event above |
The last row behaves differently from the rest. gtm4wp.contactForm7Submitted fires on every submission, including the ones that failed validation and the ones treated as spam, because the underlying Contact Form 7 event behaves that way. A successful submission therefore produces two data layer events: gtm4wp.contactForm7MailSent first, then gtm4wp.contactForm7Submitted.
Use gtm4wp.contactForm7MailSent for a conversion tag. Use gtm4wp.contactForm7Submitted only when you want to count submission attempts rather than successes.
GTM4WP 1.x: five of these events existed: gtm4wp.contactForm7InvalidInput, gtm4wp.contactForm7SpamDetected, gtm4wp.contactForm7MailSent, gtm4wp.contactForm7MailFailed and gtm4wp.contactForm7Submitted. The before submit, unaccepted and aborted events are new in 2.0.0.
What each event carries
All eight events carry the same set of keys, so a variable you build once works with any of them.
| Key | What it holds |
|---|---|
formid | The numeric ID of the form, the same number you see in the Contact Form 7 admin list. It holds (not set) when Contact Form 7 does not report one. |
formname | The title you gave the form in the Contact Form 7 admin. This is usually what you want in a report, since the numeric ID means nothing to a reader. |
unittag | The unit tag Contact Form 7 assigns to this particular form on this particular page. Use it to tell two instances of the same form apart. |
containerpostid | The ID of the post or page the form was placed on. |
locale | The locale of the form, for example en_US. |
status | The status string Contact Form 7 returned for this submission, such as sent, failed, invalid, unaccepted, spam or aborted. On the before submit event the outcome is not known yet, so do not build a condition on it there. |
inputs | A list of the submitted fields. What it contains depends on the privacy option described below. |
The form name is not part of the events Contact Form 7 fires. GTM4WP adds it to the rendered form as a data-gtm4wp-form-name attribute and reads it back from there, so it arrives as an empty string on a site where something else builds the form markup and drops the attribute.
GTM4WP 1.x: the pushes carried formid and inputs only. The form name, unit tag, container post ID, locale and status are new in 2.0.0.
Submitted field values and personal data
Consider the inputs key before you switch the integration on. Every tag in your container can read anything that is in the data layer, including tags belonging to advertising vendors, and a contact form usually collects a name, an email address and a free-text message.
The Submitted field values in the data layer option gives you three settings.
| Setting | What lands in inputs |
|---|---|
| Full: field names and submitted values (default) | A list of name and value pairs, one per field, with everything the visitor typed. |
| Field names only | A list of name entries with no values. You can still see which fields were filled in, and an optional field that stayed empty is still reported by name. |
| None | An empty list. The rest of the event, including the form ID and name, is unaffected. |
Contact Form 7 leaves out any field whose name starts with an underscore, so its own hidden fields never appear in inputs whichever setting you pick. A file upload field is reported by its file name rather than the file itself.
Your tags decide whether those values then reach an analytics or advertising tool, not the plugin. If you have no tag that needs them, Field names only or None keeps the personal data out of the data layer in the first place. See the GDPR page for the wider picture.
GTM4WP 1.x: there was no such option. Every submitted value went into the data layer, which is what the Full setting does today. A site upgrading from 1.x keeps that behavior until the option is changed.
Building the trigger in Google Tag Manager
- Open your container, go to Triggers and create a new trigger.
- Choose Custom Event as the trigger type.
- Enter
gtm4wp.contactForm7MailSentas the event name and leave This trigger fires on set to All Custom Events. - Save the trigger and assign it to your conversion tag.
To fire on one specific form rather than all of them, add a condition to the trigger. Create a Data Layer Variable named formname, then set the trigger to fire on Some Custom Events where that variable equals the form title.
To catch several of the failure events with one trigger, tick Use regex matching and enter an event name such as the following:
gtm4wp\.contactForm7(InvalidInput|Unaccepted|SpamDetected|MailFailed|Aborted)
You read every key listed in the table above the same way, with a Data Layer Variable named exactly like the key. Use the Preview mode of Google Tag Manager to see the events arrive and to check the values before you publish.
Reading a single submitted field
inputs is a list rather than an object, so a Data Layer Variable named inputs gives you the whole list. To pull one field out of it, create a Custom JavaScript variable in Google Tag Manager. The example below returns the value of the field named your-email, and expects a Data Layer Variable called inputs to exist already.
function() {
var inputs = {{inputs}} || [];
for ( var i = 0; i < inputs.length; i++ ) {
if ( 'your-email' === inputs[ i ].name ) {
return inputs[ i ].value;
}
}
return undefined;
}
This only works while the Submitted field values in the data layer option is set to Full. With the other two settings the values are not there to read.
The GA4 recommended events
Google Analytics 4 has its own naming for form tracking, and its Enhanced Measurement does not reliably pick up a Contact Form 7 submission on its own, because the form is sent over AJAX. The Also push GA4 recommended events option covers that gap. Switch it on and the tracker pushes three more events, next to the GTM4WP ones rather than instead of them.
| Event | When it fires |
|---|---|
form_start | The first time the visitor puts the cursor into a field of that form, once per form per page view |
form_submit | On every submission, matching gtm4wp.contactForm7Submitted |
generate_lead | On a successful send, matching gtm4wp.contactForm7MailSent |
All three carry the three keys GA4 expects for a form: form_id with the numeric form ID, form_name with the form title and form_destination with the address the form posts to, falling back to the current page address.
form_submit inherits the behavior of the event it is mapped to, so it fires on a failed validation as well and is not a measure of successful submissions. If you already have Enhanced Measurement form interactions enabled in your GA4 property, check your reports for a while to make sure the same submission is not counted twice.
GA4 reports a lead value when generate_lead carries value and currency. The plugin does not know what a lead is worth to you, so it does not send them. Add them in your GA4 event tag if you report on lead value.
GTM4WP 1.x: this option did not exist. Sites that need these events on 1.x have to build them from the gtm4wp.contactForm7* events with their own tags.
If the events do not arrive
- Check that the Contact Form 7 option is on. It is off by default.
- Check that the Contact Form 7 plugin itself is active. GTM4WP has nothing to listen to without it.
- Look at the Custom Event rows in Preview mode, not at the Page View row. These events arrive when the visitor submits, long after the page has loaded.
- If
inputsis empty, check the Submitted field values in the data layer option before looking anywhere else. - If
formnameis empty but the rest of the event is fine, something on your site is rendering the form without the attribute GTM4WP adds to it. Page builders and heavily customized themes are the usual cause. - If a tag fires twice on a successful submission, check whether it is on a trigger that matches both
gtm4wp.contactForm7MailSentandgtm4wp.contactForm7Submitted. Both fire, in that order. - If you renamed the data layer variable in GTM4WP, the events go into the variable you named, and your container has to be configured for the same name.
Related pages
See also the data layer variables pages for the variables GTM4WP provides on every page, how to set up triggers for visitor, device, customer and cart data for the other events that need a Custom Event trigger, the GDPR page for consent and personal data, and actions and filters if you want to change what the plugin pushes.

