How to Persist UTMs and Pass Them to a Subdomain
Persist UTMs across a click and pass them to a subdomain or another site with a small JavaScript snippet, so your funnel attribution survives. Grab the code.
How to Persist UTMs and Pass Them to a Subdomain
Need a tagged link right now? Use our UTM builder tool to generate clean UTM codes for Google Analytics in seconds. No sign-up, runs in your browser.
Here's the short version: to persist UTMs and pass them to a subdomain, save the incoming UTM parameters to the browser's Local Storage on the landing page, then read them back and append them to the outbound link before the visitor clicks through. That one move keeps your attribution intact from the marketing site all the way into the app — instead of watching it evaporate on the first click.
Because that's what UTMs do by default. They show up once, in the address bar, and then they're gone. The visitor clicks a login link to app.mysite.com, the parameters don't follow, and the campaign that drove the signup quietly disappears into the (Other) bucket where bad attribution goes to die. You spent the budget. You just can't prove it worked.
I ran into this one on a real build: an app hosted on a subdomain, a marketing site on the root domain, and a "log in" link sitting between them with no idea where the visitor came from. I want those UTMs riding along on that click so I can capture them inside the application funnel. That's the whole game — a full picture from the top of the funnel to the bottom, so you can measure campaign success and actual ROI instead of guessing.
Why UTMs vanish on a subdomain
UTM parameters live in the query string of a URL. They're not cookies, they're not session state, they're just text hanging off the end of a link. The moment someone navigates away — clicks an internal link, hits a button, follows your shiny "Start free trial" CTA into the app — that query string is gone unless you did something to keep it.
A subdomain makes it worse. mysite.com and app.mysite.com are treated as separate origins for a lot of purposes, so the UTMs that landed on your homepage have no automatic way to make the jump. Two systems, one funnel, and a gap right in the middle where the attribution leaks out.
The fix is to grab the parameters while you still have them and stash them somewhere persistent. Local Storage is perfect for this: it's simple, it survives page navigation within the same origin, and you can read it back whenever you need to rebuild the link.
The code that passes UTMs to a subdomain
The original version of this lived in an excellent post by Julius at Analytics Mania — his code was the foundation I built on. I've since made improvements and optimized it over the last few months, and the working version lives in a GitHub repo because the whitespace kept breaking when I pasted it inline. Grab it from the repo:
https://github.com/twistedx/UTMSubdomain
Read through it and you'll see it does quite a bit. By default it targets only links that carry a CSS class of outbound. If you want a generic solution that hits every link on the page, swap that targeting back to plain a tags and it'll apply to all of them. The trade-off matters more than it looks: tagging the wrong links is how you corrupt your own data.
Only tag the links that should be tagged
One rule that saves people a world of pain — don't slap UTMs onto your internal links. Tagging your own internal navigation with UTMs corrupts your attribution, because every internal click rewrites the source of the session. People do this constantly, usually by accident, then wonder why their "direct" traffic looks insane.
This is exactly why the script targets a specific outbound class instead of everything. You decide which links represent a real cross-domain handoff — the marketing site to the app, say — and you only pass UTMs across those. Consistency beats cleverness every time. The same logic applies to your tagging conventions: an off-spec medium like Email instead of email quietly splits into a separate row and muddies every report downstream.
Step-by-step: persist and pass UTMs
Here's the shape of it, so you know what the repo code is actually doing:
- Capture on arrival. When a visitor lands with UTM parameters in the URL, read them off the query string and write them to Local Storage.
- Persist across navigation. Local Storage keeps those values around as the visitor clicks through your site, so they're still there when it matters.
- Read before the handoff. When the page loads, the script finds your target links (the
outboundclass by default). - Append to the link. It rebuilds each target link's
hrefwith the stored UTMs attached, so the parameters travel with the click to the subdomain. - Capture on the other side. The app at
app.mysite.comnow receives the UTMs and can record them against the user's funnel.
The result is closed-loop: the click that started on a Google ad shows up, tagged and accounted for, inside your application. No manual stitching, no 11pm spreadsheet trying to reverse-engineer where a signup came from.
When you'd reach for a different approach
This Local Storage method is clean for marketing-site-to-app handoffs on the same root domain. If you're passing UTMs into an embedded form or an iframe rather than a full subdomain, the mechanics differ — see our guide on how to capture UTMs and pass them to an iframe. And if your goal is to land those UTMs inside a Webflow form submission, the pattern in capturing UTMs in Webflow and passing them to a form is the one you want.
Whichever route you take, the principle holds: capture the signal early, keep it somewhere it'll survive, and only attach it to the links that genuinely cross a boundary. Get that wrong and you're not measuring marketing — you're measuring your own navigation.
If you'd rather not hand-wire any of this, untangling exactly this kind of leak is what our tracking and analytics work is for. We've controlled attribution on pipelines spending hundreds of millions over the years, and it almost always comes down to the same thing: the mapping has to be correct, because one wrong value can break the whole chain. For the foundational concepts, our guide on what UTMs are and how to use them is a solid primer.
FAQ
What does it mean to persist UTMs? Persisting UTMs means saving the campaign parameters from the incoming URL so they're still available after the visitor clicks away. By default UTMs only exist in the address bar of the first page; storing them in Local Storage (or a cookie) keeps them accessible for the rest of the session.
Why don't UTMs carry over to a subdomain automatically?
UTMs live in the query string of a single URL, not in shared browser state. A subdomain like app.mysite.com is a separate context from mysite.com, so unless you explicitly read the stored parameters and append them to the cross-domain link, they don't make the jump.
Should I use Local Storage or a cookie to store UTMs? Local Storage is simpler and works well when both properties share the same root domain. A cookie scoped to the parent domain can be useful when you need values readable across subdomains directly. For most marketing-site-to-app setups, the Local Storage approach in the repo is enough.
Will this mess up my Google Analytics attribution?
Only if you tag the wrong links. Appending UTMs to internal navigation rewrites the session source and corrupts your reports. Restrict the script to genuine cross-domain handoffs — that's why it targets an outbound class rather than every link — and your attribution stays clean.
Can I make the script tag every link instead of just outbound ones?
Yes. Swap the targeting from the outbound class back to plain a tags and it will apply to all links on the page. Just be deliberate about it, because tagging internal links is the fastest way to pollute your own data.
Do I need to be a developer to implement this? Not necessarily — the repo code is copy-and-configure for a basic setup. If your funnel spans multiple tools or you're not sure which links count as cross-domain, reach out and we'll wire it so the signal path holds from click to closed-won.