Social Catnip
Back to the Blog
Essay

How to Persist UTM Parameters and Pass Them to a Subdomain

UTM parameters vanish the moment a user clicks away from your landing page. Learn how to use Local Storage to persist UTM parameters and keep your attribution intact as users move from your marketing site to your app subdomain.

By Sean Gowing
Mar 2, 20236 min read

To persist UTM parameters and pass them to a subdomain, you must save the incoming campaign values to the browser's Local Storage on your landing page, then read those values back and append them to any outbound links before the visitor clicks through. This ensures your attribution stays intact as users move from your marketing site into your application.

By default, UTMs are transient. They show up once in the address bar and then they vanish. The moment a visitor clicks a "Log In" link to app.mysite.com, those parameters don't follow. Your campaign that drove the signup quietly disappears, showing up as direct traffic or a self-referral. You spent the budget, but you can't prove it worked.

I have seen this happen more times than I care to count: one wrong value in your mapping and your whole attribution chain is broken. If you aren't capturing that signal during the handoff between your marketing site and your app, you are just guessing at ROI. That is a high-stakes game of telephone that nobody wins.

Persist UTM parameters: the short answer

UTM parameters live in the query string of a URL. They aren't cookies or session state; they are just text hanging off the end of a link. The moment someone navigates away, clicks an internal link, or hits your "Start free trial" CTA, that query string is gone unless you do something to keep it.

A subdomain makes this worse. mysite.com and app.mysite.com are treated as separate origins for many purposes, so the UTMs that landed on your homepage have no automatic way to make the jump. You end up with two systems, one funnel, and a gap in the middle where the attribution leaks out.

The fix is to grab those parameters while you still have them and stash them somewhere persistent. Local Storage is perfect for this: it is simple, survives page navigation within the same origin, and allows you to read the data back whenever you need to rebuild an outbound link.

The code that passes UTMs to a subdomain

The original version of this logic was based on excellent work by Julius at Analytics Mania, his code was the foundation I built on. I have since optimized it specifically to target links via a CSS class rather than every link on the page. You can find the working version in this GitHub repo:

<a href="https://github.com/twistedx/UTMSubdomain" target="_blank" rel="noopener">https://github.com/twistedx/UTMSubdomain</a>

By default, this script targets only links that carry a CSS class of outbound. If you want a generic solution that hits every link on the page, you can swap that targeting back to plain a tags, but be careful: tagging the wrong links is how you corrupt your own data.

Here is a rule that saves people a world of pain: do not slap UTMs onto your internal links. Tagging your own navigation with UTMs corrupts your attribution because every internal click rewrites the source of the session. People do this constantly, usually by accident, and then wonder why their "direct" traffic looks insane. It is like trying to track a delivery driver by following them home after they've already dropped the package off.

This is exactly why the script targets a specific outbound class. You decide which links represent a real cross-domain handoff, like moving from your marketing site to your app, and you only pass UTMs across those. Consistency beats cleverness every time. The same logic applies to your naming 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 is the plumbing behind the repo code:

  1. Capture on arrival. When a visitor lands with UTM parameters, the script reads them from the query string and writes them to Local Storage.
  2. Persist across navigation. Local Storage keeps those values around as the visitor clicks through your site.
  3. Read before the handoff. When the page loads, the script finds your target links (the outbound class).
  4. Append to the link. It rebuilds each target link's href with the stored UTMs attached.
  5. Capture on the other side. The app at app.mysite.com receives the UTMs and can record them against the user's funnel.

This creates a closed-loop system. The click that started on a Google ad shows up, tagged and accounted for, inside your application. No manual stitching or 11pm CSV exports required.

First-touch vs. Last-touch

When you are wiring these systems, you need to decide what you are actually measuring. Most teams focus on last-touch, but if you want the full story, you should store both.

First-touch tells you which campaign originally introduced the user to your brand. Last-touch tells you what finally pushed them over the edge. By persisting these parameters into your CRM or database via Analytics & Attribution, you can see the entire journey from the first click to the closed-won deal.

Avoiding naming chaos

Campaign reports fall apart when one person tags a link "Facebook", another "facebook" and a third "FB". This fragmentation makes it impossible to see the true impact of a channel.

To prevent this, you need a single source of truth for your links. Our UTM Builder & Link Store provides consistent, lowercase, correctly tagged campaign links from one screen. If you use our free UTM builder tool, you can ensure every link follows the same rules. For teams that need more, the module allows visitors to sign in so every link they build is saved with a short URL and a QR code, turning a free tool into a measurable lead source.

Frequently asked questions

What does it mean to persist UTMs? Persisting UTMs means saving campaign parameters from the incoming URL so they're still available after the visitor clicks away. By storing them in Local Storage, you keep 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 is treated as a separate context, so you must explicitly append the stored parameters to any cross-domain links.

Should I use Local Storage or a cookie to store UTMs? Local Storage is per-origin, meaning app.mysite.com cannot read what mysite.com stored; that is why we append the values to the link itself. A cookie scoped to the parent domain can be read by both, but it is sent with every single request to your server.

Will this mess up my Google Analytics attribution? Only if you tag your internal links with UTMs. Appending UTMs to your main navigation rewrites the session source and corrupts your data. Only use the script on genuine cross-domain handoff links.

Can I make the script tag every link instead of just outbound ones? Yes, by swapping the targeting from the outbound class to plain a tags. However, this is highly discouraged as it will pollute your attribution data with internal click noise.

Do I need to be a developer to implement this? The repo code is designed to be copy-and-configure for a basic setup. If you have a complex funnel or need custom wiring, reach out and we'll wire it so the signal path holds from click to closed-won.

Fix your attribution leaks

If your marketing data feels like a junk drawer of direct traffic and broken paths, the problem isn't your team, it is your plumbing. We have already built most of the fix through our custom modules and specialized tracking services.

Stop guessing which campaigns are working. If you want to wire up a closed-loop system that actually holds together, contact us to talk about your build.

Want this for your team?

Send us a brief and we'll come back with a fixed-price plan in 48 hours.