Using GTM to Decorate Link URLs for Improved Campaign Measurement
An iframe-embedded CRM form — the kind Pardot and HubSpot both commonly use — is treated by the browser as a completely separate document from the page that hosts it. That separation breaks campaign attribution the moment a visitor's UTM values and anonymous client ID need to follow them from the parent page into the form. This recipe decorates the iframe's own internal links with that information so the CRM can "see" it.
This builds directly on an earlier post, "Write URL query strings 2 cookies," which persists incoming campaign values to first-party cookies. As with the UTM-to-CRM recipe, the underlying inspiration traces back to Julius Fedorovicius' "Transfer UTM Parameters From One Page To Another with GTM."
This particular example targets a Pardot or HubSpot iframe lead-generation form specifically. Unlike the earlier cookie-writing solution, this one is not built as a Sandboxed JavaScript GTM template — Sandboxed JavaScript's restrictions make some of what follows impractical to implement that way.
Step 1: Persist the Incoming Campaign Information on the Parent Page
On the GTM container for the parent page — regardless of whether the iframe form appears later in the customer journey — the earlier "Write URL query strings 2 cookies" template runs as a setup tag on GA4 initialization. This persists the incoming UTM and campaign values to first-party cookies on the parent domain before anything else happens.
Step 2: Find the Iframe and Decorate Its Internal Links
Next, the parent page "listens" for an iframe whose source matches one of the known CRM hosts (Pardot, HubSpot, or similar). This is typically wired to a DOM Ready trigger; on a single-page application, the equivalent trigger would be a History Change event firing on pushState.
The decoration logic itself is built from a small set of GTM variables rather than one large inline script — each variable handles one piece of the targeting and decoration work:
| Variable | Purpose |
|---|---|
{{Parent target URL link hostname found on page}} | Holds the list of CRM hostnames (Pardot, HubSpot, etc.) to search for among the page's links and iframes, and the logic that locates matching elements. |
{{Parent decorate URL link with UTM names and values from cookies}} | Reads the UTM cookie values written in Step 1 and appends them as query string parameters onto any matching element's URL. |
{{Parent decorate URL link with _ga cookie name and value}} | Reads the _ga cookie and appends it as a query string parameter alongside the UTM values, so the iframe's CRM can recover the parent page's anonymous client ID. |
The hosts list and the page-element selectors inside these variables are meant to be edited to match your own implementation — narrow the host list to your actual CRM domains, and adjust the element selectors if your links or iframes aren't using standard tags.
Once a matching element is found, its URL is appended with the UTM values and the _ga client ID as query string parameters. This is the entire point of the exercise: because the browser treats an iframe as a wholly separate object, this is the most practical way for a CRM embedded inside that iframe to learn the parent page's anonymous browser client ID and campaign information.
Step 3: Persist the Decorated Values Inside the Iframe
Now, in the separate GTM container running inside the iframe itself, the same "Write URL query strings 2 cookies" template persists the campaign information to cookies as a setup tag on the iframe's own GA4 initialization.
On the GTM Configuration Settings variable inside the iframe container, the iframe's URL is checked for the parent's anonymous browser client ID that was appended in Step 2. If that value is absent — for example, if the visitor arrived at the iframe directly rather than through the decorated link — the client ID instead falls back to whatever value the Analytics Storage variable type can source from the iframe's own context.
Step 4: Populate the Hidden Form Fields
From here, sourcing the campaign information into hidden fields on the CRM form can vary considerably depending on your implementation. If a value like utm_campaign=my_campaign_name has traveled the full path — from the parent site URL, to the parent cookie, to the iframe URL, to the iframe cookie — the code populating a like-named hidden field on the iframe's CRM form might resemble the following:
<script>
function getCookie(name) {
// JavaScript code for reading cookies goes here...
}
document.querySelector('input[data-sc-field-name="utm_campaign"]').value = getCookie('utm_campaign');
</script>
The trigger for this tag is most likely a visibility trigger tied to the form's page element, ensuring the field is populated only once the form has actually rendered in the DOM.
Where to Get the Full Solution
The entire solution — including the parent and iframe container templates referenced above — is available as a downloadable JSON file that can be imported into a GTM container:
View on GitHub: decorate-link-URL-for-improved-campaign-measurement
What This Solution Doesn't Cover
This solution does not address getting the conversion metric from the CRM form submission back to the parent page. For that side of the problem, Simo Ahava's write-up on cross-site iframe messaging is the right place to look:
"How Do I Use The postMessage() Method With Cross-site Iframes?"
Attribution
The foundational approach to transferring UTM parameters across pages — which this iframe-specific variant builds on — comes from Julius Fedorovicius:
"Transfer UTM Parameters From One Page To Another with GTM"
https://www.analyticsmania.com/post/transfer-utm-parameters-google-tag-manager/
This post is a Blogger rewrite of an earlier piece originally published on Weebly. The original post illustrated the parent-page and iframe-container configuration with a series of screenshots; the variable table above describes those same building blocks based on the variable names and logic referenced directly in the post text.
Comments
Post a Comment