A practical guide to forwarding campaign query strings through redirects, testing every hop, and keeping GA4 attribution useful.
You can build a clean campaign URL and still lose attribution before the visitor reaches the landing page.
The usual culprit is not GA4, the ad platform, or the frontend tag. It is a redirect that sends the browser to the right page while quietly dropping the query string.
This matters for developers because redirects often live in infrastructure: Nginx, Apache, edge middleware, short-link tools, CMS routing, or application code. If one hop in the chain forgets ?utmsource=..., the final page cannot recover it.
This guide walks through redirect patterns that preserve UTM parameters without creating duplicate keys, unsafe destinations, or analytics noise.
The redirect is valid HTTP. The user lands on the right page. But the destination URL no longer contains utmsource, utmmedium, or utmcampaign, so GA4 sees a pageview without the campaign context you expected.
Status code correctness and attribution correctness are separate checks. A 301, 302, 307, or 308 can all preserve or drop parameters depending on how the Location header is built.
Use a permanent redirect such as 301 or 308 when the source URL has permanently moved. Use a temporary redirect such as 302 or 307 for short-lived campaigns, experiments, or links whose destination may change.
Do not use a permanent redirect just because it works in a quick test. Browsers, crawlers, and intermediate systems may cache permanent redirects, which makes later campaign changes harder to reason about.
The query-forwarding rule should be intentional either way: if the campaign URL arrives with tracking parameters, every redirect hop that owns the route should preserve them.
For a destination with no existing query string, append the incoming query safely with $isargs$args:
$isargs expands to ? only when the incoming request has a query string. $args contains the raw incoming query string. That avoids producing a trailing ? for untagged visits.
That simple version is fine only if you have tested the empty-query case and accept the trailing &. For stricter output, split tagged and untagged cases or move the logic into application/edge code where URL parsing is clearer.
QSA means query string append. It is especially important when the replacement URL includes its own query string, because without explicit handling the original parameters may be replaced.
This preserves duplicate keys exactly as received. If you prefer one value per UTM key, use set() for known campaign keys instead of append():
The important part is that URL encoding is handled by URL and URLSearchParams. Do not build redirect URLs by stitching raw request values into a string.
Different tools may choose the first value, the last value, or expose both. That ambiguity is bad for attribution.
Pick a policy before launch: Source-owned UTMs win: the incoming campaign parameters replace destination defaults. Destination-owned UTMs win: the landing page keeps its predefined campaign values. Reject duplicates: the redirect refuses or normalizes links with conflicting campaign keys.
