A Contact Form 7 submission can arrive with the name, email, and message perfectly intact while every campaign field is blank.
That is not a small reporting problem. It means the lead survived, but the reason the lead arrived did not.
When I need to pass UTM parameters through Contact Form 7, I treat it as a four-part handoff: capture the values, put them into real CF7 hidden fields, add the matching mail tags, and verify the final destination.
If any one of those steps is missing, the form can still show a success message. That is why this problem stays hidden.
The short version
| What you see | Most likely break | What I check first |
|---|---|---|
| Hidden input is empty before submit | Capture script, form-tag setup, caching, or script order | Inspect the rendered input value in the browser |
| Input has a value, but email is blank | Mail tag is missing or does not match the form field name | Compare the Form and Mail tabs character for character |
| Email shows a shortcode or form tag literally | WordPress shortcode syntax and CF7 form-tag syntax were mixed | Replace the legacy wrapper with a current CF7 hidden tag |
| Email has the value, but CRM is blank | Webhook, Zapier, or CRM field mapping | Inspect the outgoing payload and destination mapping |
| Tagged landing page works sometimes | Cache or loss between landing and conversion pages | Test a cached page and an untagged second page separately |
The important distinction is between a form tag and a mail tag.
The form tag creates the field that receives the value. The mail tag tells Contact Form 7 where to place the submitted value in the email template.
You usually need both.
Method 1: capture UTM parameters from the current URL
Contact Form 7 has a native hidden form tag. Its current source registers the hidden tag and renders it as a real HTML hidden input. The same source supports a default:get option that reads a query-string value with the same field name.
For a form that appears on the tagged landing page, the exact form-tag pattern is:
[hidden utm_source default:get]
[hidden utm_medium default:get]
[hidden utm_campaign default:get]
[hidden utm_term default:get]
[hidden utm_content default:get]
A visitor opening this URL:
https://www.example.com/contact/?utm_source=google&utm_medium=cpc&utm_campaign=summer_demo
should get a rendered input whose name is utm_source and whose value is google.
This method is simple. It is also narrow.
It reads the current request. If the visitor lands on one page, browses to an untagged contact page, and submits there, the contact page no longer has the original UTMs. A hidden field cannot remember a value it never received.
It is also sensitive to full-page caching because the hidden value is created when WordPress renders the form. If a cache ignores query strings and serves the same HTML to everyone, the form can receive an old or empty value.
How a CF7 hidden field becomes a mail value
The full path is worth spelling out because the square brackets can make the setup look more magical than it is.
- Contact Form 7 reads
[hidden utm_source default:get]in the Form tab. - On a request containing
utm_source=google, CF7 renders an HTML input namedutm_sourcewith the valuegoogle. - The browser includes that hidden input when the visitor submits the form.
- Contact Form 7 receives
utm_sourceas posted form data. - The
mail tag inserts that posted value into the Mail template.
The mail tag does not go back to the landing URL and look for UTMs. It reads the submitted field. This is why adding to the Mail tab without adding a form field does not create tracking.
The same boundary applies to every destination after Contact Form 7. Each step receives a named value from the step before it.
| Layer | Native example | HandL example |
|---|---|---|
| CF7 form field name | utm_source | utm_source_cf7 |
| Rendered HTML input name | utm_source | utm_source_cf7 |
| CF7 mail tag | | [utm_source_cf7] |
| Webhook or Zapier input | utm_source | utm_source_cf7 |
| CRM destination | Whatever CRM field you map | Whatever CRM field you map |
I do not rename fields casually after a form is live. A rename in the Form tab can break the Mail tab, stored-message expectations, Zapier samples, webhook payloads, and CRM mappings at the same time.
Method 2: capture once and populate the CF7 fields later
For a normal lead journey, I prefer to capture the attribution on arrival and keep it until the visitor converts.
That is the role of a persistence layer. It reads the landing values, stores them according to the site’s consent setup, and populates the form when Contact Form 7 is ready.
HandL UTM Grabber is one way to do this on WordPress. Its current Contact Form 7 integration guide uses these field tags:
[hidden utm_source_cf7 utm_source_cf7-{formid} class:utm_source id:utm_source]
[hidden utm_medium_cf7 utm_medium_cf7-{formid} class:utm_medium id:utm_medium]
[hidden utm_campaign_cf7 utm_campaign_cf7-{formid} class:utm_campaign id:utm_campaign]
[hidden utm_term_cf7 utm_term_cf7-{formid} class:utm_term id:utm_term]
[hidden utm_content_cf7 utm_content_cf7-{formid} class:utm_content id:utm_content]
The current integration also provides a UTM Fields (HandL) tag generator that inserts the fields for you. I would use that instead of copying the wrapper syntax from the 2018 version of this article.
The field name matters. In this setup, the submitted source field is utm_source_cf7, not utm_source. The Mail tab, webhook, Zapier step, and CRM mapping must use the name that your form actually submits.
This is where a “tracking” problem can really be a naming problem.
Add the matching mail tags
After adding hidden fields under the Form tab, open the Mail tab and include the matching mail tags in the message body.
For the native fields from Method 1:
Campaign source:
Campaign medium:
Campaign name:
Campaign term:
Campaign content:
For the HandL fields from Method 2:
Campaign source: [utm_source_cf7]
Campaign medium: [utm_medium_cf7]
Campaign name: [utm_campaign_cf7]
Campaign term: [utm_term_cf7]
Campaign content: [utm_content_cf7]
Contact Form 7 matches the mail tag to the submitted form field by name. If the Form tab says utm_campaign_cf7 and the Mail tab says utm_campaign, those are two different fields.
Where the value goes after submit
| Destination | What receives the value | What you must configure |
|---|---|---|
| Email notification | The CF7 mail tag in the Mail template | Add the mail tag that matches the hidden form field |
| Flamingo | The submitted field in the stored inbound message | Install and activate Flamingo; confirm the field appears in the saved message |
| CRM webhook or connector | The outgoing payload field | Map the submitted CF7 field name to the correct CRM property |
| Zapier | The field exposed by the Contact Form 7 trigger or connector | Refresh the trigger sample, then map that field in the next Zap step |
Contact Form 7 does not store submitted messages by itself. The official Flamingo plugin exists for that job and stores the submission data in the WordPress database.
A webhook or CRM handoff is separate again. Contact Form 7’s default plugin description says it does not send data to external servers by itself. An integration has to do that work.
If you are using Zapier, keep the handoff as a separate step. My Zapier for Contact Form 7 walkthrough covers that downstream path. I would not merge it into this setup because the two pages answer different questions: this one gets the value into the submission; the Zapier article moves it onward.
Why Contact Form 7 UTM tracking breaks
1. The hidden field renders empty
Inspect the field before submitting. For the native setup, run this in the browser console:
document.querySelector('input[name="utm_source"]')?.value
For the HandL setup, use utm_source_cf7 instead.
If the result is empty, the failure happened before the mail or CRM step. Check whether the URL contains the value, whether the form tag is correct, and whether the capture script runs after the form field exists.
This script-order problem appears with forms loaded in a popup, modal, or delayed page section. Custom JavaScript can run successfully and still find nothing because Contact Form 7 has not rendered the input yet.
2. The value arrives as a literal shortcode string
Contact Form 7 form tags and WordPress shortcodes are not interchangeable.
If an email contains something like [utm_source_i] or the page prints the wrapper instead of a hidden input, the syntax was inserted into a context that no longer evaluates it. Replace the legacy wrapper with a current CF7 hidden tag or the current HandL tag generator output.
3. The email has the value, but the CRM does not
This is good news, in a limited way. The browser capture and Contact Form 7 submission worked.
Now inspect the outgoing webhook or Zapier sample. Confirm that the payload contains the field, then compare its name with the destination CRM property. My broader blank UTM field diagnosis follows the same boundary: find the last place where the value is present, then fix the next handoff.
Caching is the first infrastructure layer I check
A Contact Form 7 page can look normal while its hidden attribution fields are stale.
The native default:get method is evaluated while the server renders the form. If a page cache does not vary by the query string, it can reuse HTML that was created without the current visitor’s UTMs.
A persistent tracking setup has a different risk. The form HTML may be fine, but an aggressive cache or CDN can ignore the query args or cookies that the attribution layer needs.
I do not start by replacing Contact Form 7. I check the cache behavior. My WordPress hosting and UTM tracking guide shows the layers I inspect: host cache, CDN, Varnish, WordPress cache plugin, query-string rules, and cookie exclusions.
- Purge the page cache after changing the form.
- Test with a unique campaign value that has never been used before.
- Check the response headers to see whether the page came from cache.
- Verify the input value in the rendered DOM, not only the visible form.
- Repeat on an untagged second page if the setup is supposed to persist attribution.
How I verify UTM parameters in Contact Form 7
- Open a tagged URL with unmistakable values:
?utm_source=cf7_qa&utm_medium=manual&utm_campaign=contact_form_test_202608. - Inspect the hidden input in the browser and confirm its value before submitting.
- Submit with a unique email address so the test is easy to find.
- Open the email notification and confirm source, medium, and campaign separately.
- If Flamingo is active, open the saved inbound message and confirm the same fields.
- If a webhook or Zapier sends the lead onward, inspect the run data before checking the CRM.
- Open the CRM record and confirm the mapped destination fields.
- If persistence is expected, repeat the test after navigating to an untagged page.
I also test one direct or untagged visit. A good setup should not invent campaign values when no campaign data exists.
If you are choosing a form plugin rather than fixing an existing Contact Form 7 setup, my WordPress form plugin lead-tracking audit compares that broader decision. This article assumes you already use CF7 and want the handoff to work.
What I would not over-claim
- A populated hidden field proves the value reached the form. It does not prove the CRM stored it.
- An email notification is not a submission archive. Use Flamingo or another storage layer if you need a database record.
- A webhook does not fix missing attribution. It only moves the values it receives.
- The native
default:getmethod does not preserve UTMs across pages. - A persistence plugin does not override consent choices or a cache that is configured to ignore the required query strings and cookies.
- Contact Form 7 is not automatically the best or worst form plugin for attribution. The implementation decides whether the data survives.
My final checklist
- Use a current CF7 hidden form tag.
- Choose current-URL capture or persistent capture deliberately.
- Match every Form-tab field name to its Mail-tab tag.
- Add the field to the webhook, Zapier step, or CRM mapping separately.
- Inspect caching before blaming the form plugin.
- Verify the rendered hidden input before submit.
- Verify email, storage, integration payload, and CRM as separate checkpoints.
- Repeat the test after navigating away from the tagged landing page.
The form is not finished when it says “sent.” It is finished when the same campaign value reaches every place that depends on it.
