
Quick answer: There are two ways to capture UTM parameters in Gravity Forms. If visitors land directly on your form page, use the built-in method: add a Hidden field for each parameter, enable Allow field to be populated dynamically, and set the Parameter Name to utm_source, utm_medium, and so on. Gravity Forms fills the fields from the URL automatically. No code needed.
If visitors land on one page and submit the form on another, the UTM parameters fall off the URL in between, and the built-in method captures nothing. For that, add the JavaScript snippet below to hold the values across pages. The same setup also captures Google Ads click IDs (gclid, gbraid, wbraid).
Table of Contents
Which method do you need?
| Your situation | Use this |
|---|---|
| Visitors land directly on the form page and stay there (the UTM-tagged URL is the form page) | Method 1: built-in Dynamic Population. Stop there. |
| Visitors land on a blog post or landing page first, then navigate to the form on another URL | Method 1 plus Method 2 (the snippet) |
| You run ads and the landing page is not the form page | Method 1 plus Method 2 |
| You need lead attribution across multiple sessions regardless of whether url parameters exist | A plugin: see below |
The built-in method is the base layer for all of these. Set it up first.
Method 1: Built-in Dynamic Population (no code)
This is all you need when the UTM-tagged URL is the same page the form lives on. Gravity Forms reads the query string and populates matching hidden fields on load.
Step 1 – Add a Hidden field for each parameter
In the Gravity Forms editor, add a Hidden field for every value you want to capture: UTM Source, UTM Medium, UTM Campaign, UTM Content, UTM Term, and (if you run Google Ads) GCLID, GBRAID, and WBRAID. Give each a clear internal label.
Step 2 – Enable dynamic population
Open each field’s Advanced tab, check Allow field to be populated dynamically, and set the Parameter Name to the exact key from the URL:
| Hidden field | Parameter Name |
|---|---|
| UTM Source | utm_source |
| UTM Medium | utm_medium |
| UTM Campaign | utm_campaign |
| UTM Content | utm_content |
| UTM Term | utm_term |
| GCLID | gclid |
| GBRAID | gbraid |
| WBRAID | wbraid |
Step 3 – Send tagged traffic
Point campaigns at a URL like:
https://yoursite.com/contact/?utm_source=google&utm_medium=cpc&utm_campaign=summer_sale
When the form loads, each hidden field fills with its matching value and stores it on the entry. Every submission now carries source data attached to the lead, not just an anonymous session in Google Analytics.
The limitation: this only works if the UTM parameters are still in the URL at the moment the form loads. The instant a visitor clicks an internal link, the parameters fall off the address bar. If your form lives on a different page from your landing page, Method 1 alone captures nothing. That’s what Method 2 fixes.
Method 2: Capture UTMs across pages in a session (JavaScript snippet)
This snippet captures UTM parameters and Google Ads click IDs the moment a visitor arrives on any page, holds them for the rest of the browser session, and writes them into your hidden fields when the form loads. Even if the visitor browsed five pages first, the data is still there.
// Replace 'myclass' and 'myitem' with your own handle.
(function () {
var fieldMap = {
'utm_source': '.myclass-utm-source input',
'utm_medium': '.myclass-utm-medium input',
'utm_campaign': '.myclass-utm-campaign input',
'utm_content': '.myclass-utm-content input',
'utm_term': '.myclass-utm-term input',
'gclid': '.myclass-gclid input',
'gbraid': '.myclass-gbraid input',
'wbraid': '.myclass-wbraid input'
};
// Capture any tracked params in the current URL (runs on every page).
var params = new URLSearchParams(window.location.search);
Object.keys(fieldMap).forEach(function (key) {
var value = params.get(key);
if (value) sessionStorage.setItem('myitem_' + key, value);
});
// Populate matching hidden fields from storage.
function populate() {
Object.keys(fieldMap).forEach(function (key) {
var stored = sessionStorage.getItem('myitem_' + key);
if (!stored) return;
document.querySelectorAll(fieldMap[key]).forEach(function (input) {
if (!input.value) input.value = stored;
});
});
}
// Runs as soon as the DOM is ready.
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', populate);
} else {
populate();
}
// Runs again after Gravity Forms loads a form with AJAX
if (window.jQuery) {
jQuery(document).on('gform_post_render', populate);
}
})();
Step 1 – Add the script site-wide
It must run on every page, not just the form page. In WordPress, any of these work:
- Add it to your theme’s
functions.phpviawp_headorwp_enqueue_scripts - Paste it into a code-snippets plugin like WPCode or Code Snippets, enabled site-wide
- Add it to Google Tag Manager as a Custom HTML tag firing on all pages (the
readyStateguard above is what makes the GTM path reliable)
Step 2 – Confirm your hidden fields exist
Use the same Hidden fields from Method 1. Keep Allow field to be populated dynamically enabled. That way direct-landing visits are still captured natively, and the snippet only fills in the cross-page ones.
Step 3 – Add a CSS class to each field
On each hidden field’s Appearance tab, add a CSS Class so the script can find it:
| Hidden field | CSS Class |
|---|---|
| UTM Source | myclass-utm-source |
| UTM Medium | myclass-utm-medium |
| UTM Campaign | myclass-utm-campaign |
| UTM Content | myclass-utm-content |
| UTM Term | myclass-utm-term |
| GCLID | myclass-gclid |
| GBRAID | myclass-gbraid |
| WBRAID | myclass-wbraid |
Step 4 – Test the cross-page flow
- Visit any page with test parameters:
yoursite.com/blog/some-post/?utm_source=test&utm_campaign=snippet_test - Click an internal link to the page with your form, do not refresh
- Fill out and submit the form
- Open the entry in your Gravity Forms dashboard. UTM Source and UTM Campaign should read
testandsnippet_test
If the fields are empty, open dev tools and confirm each hidden field’s container has the right CSS class applied.
How the snippet works
On every page load it reads the URL. If any tracked parameter is present, it saves each one to sessionStorage under your custom key (myitem_utm_source, etc.). That’s what carries the data across pages. Once captured on the landing page, it stays available for the whole browser session.
When the form exists on-page, it populates the hidden fields by CSS class, but only if a field is still empty. So it never overwrites a value Gravity Forms already captured. The readyState check runs the populate step immediately if the page has already loaded (the GTM case), and the gform_post_render hook re-runs it for forms that load with AJAX.
Why sessionStorage and not localStorage? It clears when the browser session ends, which gives you clean last-touch-per-session data. You won’t accidentally stamp a brand-new conversion with a UTM from a campaign the visitor clicked weeks ago. If you do want values to persist across sessions, swap sessionStorage for localStorage. Read the limits below first, though, because real multi-session attribution needs more than a storage swap.
What this snippet does NOT do
The snippet does not capture the full story, here’s what you need to know:
❌ First-touch across multiple sessions. When the session ends, sessionStorage clears. A visitor who clicks your ad today and converts three weeks later from a bookmark has no first-touch data left.
❌ Landing page URLs. It captures parameters, not the first-page or conversion-page URL. That’s useful attribution context it doesn’t grab.
❌ Referrer fallback. A visitor arriving from organic search or a partner link with no UTMs leaves nothing to capture. You’d need to extend the script to fall back to document.referrer.
❌ Cross-device tracking. Storage is per-browser, per-device. Mobile today and desktop tomorrow are two unconnected sessions.
❌ Visitors who block storage APIs. Some privacy tools disable sessionStorage; those visitors submit with no captured data.
For single-session campaigns where you control the landing page and the funnel is short, this snippet is enough. For real multi-session attribution, you need more.
For multi-session attribution
If your sales cycle is longer than one browser session, or you want to know where a submission came from regardless of the source, and regardless of utm parameters, you need more than a snippet.
Our Referral Tracking for Gravity Forms plugin handles all of the above out of the box:
- Captures first-touch and last-touch sources across sessions
- Stores referrer data on every entry regardless of the source (Google, Bing, AI-Search engines etc – anything)
- Includes referrer when UTM parameters aren’t present
- Works with any form on your site without per-form setup
- Writes directly into Gravity Forms entry fields for clean CRM mapping
Get Referral Tracking for Gravity Forms
Using WPForms instead? There’s a Referral Tracking for WPForms plugin with the same functionality.
FAQ
Does Gravity Forms capture UTM parameters automatically?
Not by default. You add a Hidden field for each parameter and enable Allow field to be populated dynamically with the matching parameter name. After that, Gravity Forms fills the fields from the URL on its own, but only when the parameters are present on the page where the form loads.
How do I pass UTM parameters into a Gravity Form?
Add hidden fields, turn on dynamic population (Method 1 above), and send traffic to URLs with UTM tags like ?utm_source=google&utm_medium=cpc. The values land on the entry when the form is submitted.
Why aren’t my UTM parameters being captured?
Almost always because the visitor landed on a different page than the form. The parameters drop off the URL as soon as they click an internal link, so there’s nothing left for Gravity Forms to read by the time the form loads. The JavaScript snippet (Method 2) fixes this.
Do I need a plugin to capture UTMs in Gravity Forms?
No. Native dynamic population needs no code, and the snippet handles cross-page capture for free. A plugin only earns its keep once you need multi-session attribution, landing-page URLs, or referrer fallback.
Can Gravity Forms capture Google Ads click IDs (gclid, gbraid, wbraid)?
Yes, the same way it captures UTMs. Add a hidden field for each, set the parameter name to gclid, gbraid, or wbraid, and they’re picked up natively on direct visits and by the snippet across pages.
What’s the difference between tracking UTMs in Google Analytics and in Gravity Forms?
Analytics gives you aggregate session reports, like “47 visits from a LinkedIn campaign.” Capturing UTMs in Gravity Forms ties the source data to the individual lead, so you can see which specific submission came from which campaign.