How to Capture UTM Parameters and Page URLs in Webflow Forms
If you're running paid ads, email campaigns, or referral programs through your Webflow site, you need to know where your leads are actually coming from. UTM parameters tell you the source. The page URL tells you which page converted. Without both, you're guessing.
I recently set this up for a solar client running multiple paid campaigns through a single sitewide form. Instead of building a separate form for every campaign and every page, we used a small JavaScript snippet to capture full attribution automatically: landing page, referrer, UTM parameters, and Google and Facebook click IDs, all stored on the first page view of the session.
This walkthrough uses Webflow as the example (that's where the client was), but the technique works on almost any form that accepts custom code or hidden fields. If you're on WordPress, Framer, a custom HTML site, or anywhere you can drop a script into the <head> or before </body>, the logic is the same. Only the steps for finding the form settings change.
Why this matters
Most form submissions tell you almost nothing about where the lead came from. You get a name, an email, maybe a message. What you don't get: whether this person found you through a LinkedIn ad, a Google search, a referral from another site, or typed your URL directly.
A working attribution setup captures ten data points on every form submission:
- Landing page — the first URL the visitor hit this session. If they landed on
/services/marketing-systems-diagnosisfrom a paid ad, that's what gets stored, not wherever they ended up submitting from. - Submitted from — the URL they were on when they actually submitted the form (often
/contactor a services sub-page). - Referrer — the external site that sent them (
google.com,linkedin.com, a direct email, etc.). - Five UTM parameters —
utm_source,utm_medium,utm_campaign,utm_term,utm_content. - Google Click ID (
gclid) — Google Ads attaches this to every click. It's how Google Ads matches conversions back to specific ads, keywords, and bids. - Facebook Click ID (
fbclid) — Meta's equivalent for Facebook and Instagram ads.
Capturing all ten gives you full-picture attribution: not just "this lead came from a paid campaign" but "this lead came from the spring brand campaign's services-page-v2 ad variant on Google, clicked through to /services/marketing-systems-diagnosis, browsed for six minutes, and submitted on /contact."
The "first page view" principle
Here's a mistake most DIY attribution setups make: they capture the URL and UTMs at form submission time. That sounds right but it's wrong. By the time someone submits a form, they've usually navigated away from the page they originally landed on.
If someone clicks a LinkedIn ad and lands on /services/marketing-systems-pilot, browses around, and eventually submits the contact form from /contact, the UTMs are gone. The /contact page doesn't have utm_source=linkedin in its URL — that was on the landing page, three clicks ago.
The fix is to capture attribution data on the first page view of the session, store it temporarily, and read it back at form submission time. That way you always get the ad, not the fallback.
Step 1: Add hidden fields to your form
Open your Form Block in Webflow, drop an Embed element inside the form, and paste this:
<input type="hidden" class="landing_page" name="landing_page" value="">
<input type="hidden" class="submitted_from" name="submitted_from" value="">
<input type="hidden" class="referrer" name="referrer" value="">
<input type="hidden" class="utm_source" name="utm_source" value="">
<input type="hidden" class="utm_medium" name="utm_medium" value="">
<input type="hidden" class="utm_campaign" name="utm_campaign" value="">
<input type="hidden" class="utm_term" name="utm_term" value="">
<input type="hidden" class="utm_content" name="utm_content" value="">
<input type="hidden" class="gclid" name="gclid" value="">
<input type="hidden" class="fbclid" name="fbclid" value="">These ten fields stay invisible to visitors but submit alongside the rest of the form. The script in Step 2 fills them in automatically.
Step 2: Add the JavaScript
Go to Page Settings, then Custom Code, and paste this inside the Before </body> tag section:
<script>
(function() {
const STORAGE_KEY = 'lead_attribution';
const UTM_PARAMS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'];
const CLICK_IDS = ['gclid', 'fbclid'];
// Only capture on the first page view of the session
if (sessionStorage.getItem(STORAGE_KEY)) {
fillFormFields();
return;
}
const params = new URLSearchParams(window.location.search);
const attribution = {
landing_page: window.location.pathname + window.location.search,
referrer: document.referrer || '',
};
UTM_PARAMS.forEach(key => {
attribution[key] = params.get(key) || '';
});
CLICK_IDS.forEach(key => {
attribution[key] = params.get(key) || '';
});
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(attribution));
fillFormFields();
function fillFormFields() {
const data = JSON.parse(sessionStorage.getItem(STORAGE_KEY) || '{}');
// Capture where they're submitting from at the moment the form loads
data.submitted_from = window.location.pathname + window.location.search;
Object.keys(data).forEach(key => {
const nodes = document.getElementsByClassName(key);
for (let i = 0; i < nodes.length; i++) {
nodes[i].value = data[key];
}
});
}
})();
</script>A few things about this script that are worth understanding:
- No external dependencies. The old version of this script commonly relied on js-cookie. This version uses sessionStorage directly and needs no libraries.
- First-page-view capture. The
if (sessionStorage.getItem(STORAGE_KEY))check ensures attribution is only captured on the first page view of the session. Subsequent page views don't overwrite the data. - `submitted_from` is captured live. Unlike the other fields, this one updates every time the form loads. So the form captures where the visitor originally landed (
landing_page) AND where they actually submitted from (submitted_from). - Empty string instead of null. Missing values become empty strings rather than
"null"as text, which is easier to filter and sort inside your CRM.
Step 3: Test it
Publish your site, then visit a page with a fully tagged URL:
https://yourwebsite.com/services?utm_source=google&utm_medium=cpc&utm_campaign=spring&gclid=test123Open DevTools, go to Application → Session Storage, and confirm a lead_attribution entry exists with all your values captured. Navigate to a different page without UTMs (for example, /contact) — the sessionStorage entry should NOT be overwritten.
Submit a test form and open the submission inside your form handler (Webflow Forms tab, HubSpot, or wherever submissions land). You should see all ten fields populated:
landing_pagereflects the original URL with UTMssubmitted_fromreflects wherever you actually submitted (likely/contact)referrerreflects the external site that sent you- All five UTM fields populated
gclidpopulated,fbclidempty (we only passed one click ID)
Most common causes of empty fields: the Embed element sitting outside the Form Block in Webflow, the script loading on a page where the form doesn't exist, or a typo in one of the class names. The class on each hidden input has to match the data key exactly.
Handling this the right way from a privacy standpoint
This approach sidesteps the cookie consent conversation entirely, which is the point.
sessionStorage is tab-scoped and session-only. When the visitor closes the tab, the data vanishes. Privacy regulations (GDPR, CCPA/CPRA, state privacy laws in Virginia, Colorado, Connecticut, and the rest) generally don't treat session-only storage as tracking the same way persistent cookies are treated. There's no cookie consent banner requirement for sessionStorage data in most jurisdictions.
What does create a privacy obligation is the moment this attribution data gets attached to a form submission. At that point, you're storing personal information (name, email, phone, plus the attribution context) in your CRM, and that triggers the standard set of obligations:
- Disclose what you collect. Your privacy policy should list exactly what attribution data you capture with form submissions (UTMs, referrer, landing page, click IDs) and what you use it for (responding to the inquiry, understanding how leads find you).
- Disclose why. "Responding to your inquiry" and "understanding how people find our site" are both legitimate-interest purposes under GDPR and standard business-purpose collection under CCPA/CPRA.
- Honor data rights. If someone asks what information you hold about them, the attribution fields are part of their record. Same for deletion requests.
Where it still gets dicier is if you're also passing this attribution data to a third-party ad platform — Meta Pixel's Lead event, Google Ads enhanced conversions, LinkedIn Insight Tag conversions. Those cookies and events require explicit marketing consent because they ARE cross-site tracking and they DO create user profiles across sites.
Practical setup for a service business capturing leads from paid campaigns:
- Keep attribution capture ungated. sessionStorage + server-side capture at form submit time is transactional, not tracking.
- Gate ad-platform conversion tags behind a marketing consent toggle. Use a cookie consent manager that blocks scripts until a user accepts.
- Add a form-level disclosure that points to your privacy policy, linked from below every submit button.
I am not a lawyer, and consent law varies by jurisdiction (and changes fast). If you're running meaningful volume through paid campaigns, or if a significant share of your traffic comes from the EU or California, have an actual privacy lawyer review your setup before you scale.
What about server-side tracking?
Client-side UTM capture, whether it uses cookies or sessionStorage, is the most common approach, and it's what this post covers. It's fast to implement and works fine for most service businesses. But in 2026, it's worth knowing the tradeoffs.
Client-side attribution (whether it uses cookies or sessionStorage) has some fragility. Safari strips cookies after seven days under Intelligent Tracking Prevention, ad blockers sometimes clear URL parameters on navigation, and certain privacy-focused browsers block script execution entirely. sessionStorage avoids most of these issues but still requires the script to run on the first page view to capture anything. For a service business receiving a steady flow of form submissions, these gaps usually don't matter much. For a site running hundreds of paid conversions per month, they add up.
The more durable alternative is to capture UTM parameters server-side at the moment of form submission, rather than storing them in a cookie first. Your form backend (HubSpot, Webflow's form handler, a Zapier webhook, a custom endpoint) reads the document.referrer and URL parameters at submit time and writes them directly to your CRM.
Server-side attribution doesn't need cookies, doesn't care about ITP, and survives ad blockers. It does require a bit more plumbing, and it loses the cross-page persistence that the cookie approach gives you (someone who lands on an ad and submits four pages later loses the UTM data unless you pass it through the URL manually).
For most of the people reading this post, the client-side cookie approach is the right tradeoff: simpler, faster to ship, and precise enough to inform real decisions. If you're scaling past that, it's worth moving to a server-side setup.
What this unlocked for the solar client
Before this setup, they had no idea whether leads were coming from Google Ads, Facebook, organic search, or which landing page was doing the actual work. After:
- Every lead carried its source, medium, and campaign
- They could compare cost per lead across channels with real numbers
- Google Ads and Meta Ads conversion reports could match back to specific clicks via
gclidandfbclid, instead of estimating attribution - One form served the entire site instead of one per campaign
- Landing page performance became measurable instead of theoretical
Find the other gaps in your lead system
Tracking is one piece of a working lead system. If you want to see where else your site might be leaking leads, run the free website assessment. Ten minutes, no call required.
If the gaps run deeper than tracking, the Marketing Systems Diagnosis at $2,000 maps your full system end to end so you know exactly what to fix and in what order.
UTM tracking is one component of a working lead system. If you want the full framework, read The Lead Operating System: How Service Businesses Turn Traffic Into Revenue.
Have questions? Connect with me on LinkedIn and send a message.
Marketing Systems Consultant. I help service businesses find and fix the gaps between their website, leads, and sales.