Forms in a cross-domain iframe
A form that lives on its own domain and is embedded on the main site in an <iframe> — a configurator on builder.client.com inside client.com, a booking flow on a separate app domain. Install the same tag on both sites and the frame picks up the journey the visitor actually took on the parent page. Nothing else to add.
Why the frame can't see the journey on its own
MTK Attribution keeps the journey in the browser's localStorage, which is scoped per origin. The frame is a different origin from the page around it, so a tag running inside it starts from an empty journey and sees the parent page as its referrer. Every lead through that form would read Direct or Referral from the main site, whatever ad brought the visitor there.
The parent page can't reach into a cross-origin frame either, so the tag on the main site can't fill those fields itself. What the two pages can do is talk to each other, and that is what the bridge does.
Setup
- Install the tag on the main site as usual, in the page
<head>(Dashboard → the client's license → Install). - Install the same tag on the form's domain, in that site's
<head>. Same license, same snippet — the frame needs its own copy because it is its own page. Add the form domain to the license's allowed domains if that list is in use. - Add the hidden fields to the embedded form exactly as on any other platform: by HTML name, or with a
[mtk:…]token in the value. The frame fills them from the parent's journey.
<script src="https://attr.boggsmtk.com/v1/tag.js?k=YOUR_LICENSE_KEY"></script>
Same domain, different subdomain (the usual case, builder.client.com inside client.com): nothing to configure. The two sides trust each other because they share a domain.
Two unrelated domains (the form is on forms.otheragency.com): each side has to name the other. In the tag config for the main site's license, put the form's hostname under Embedded form domains; in the tag config for the form's license, put the main site's hostname there. A one-sided entry is refused, on purpose. If both sites are on one license, one entry covers both directions.
Keep both loaders out of GTM, for the same reason as always: a blocked container on either side takes that side of the bridge down with it.
How it works
- The frame asks.When the tag finds itself inside an iframe, it posts a request to the parent page every 300 ms for up to 15 seconds. The parent's tag may finish after the frame has loaded, hence the retries. The request carries no data.
- The parent answers, if it trusts the frame. Once its own journey is written, the parent replies with its stored journey and the URL the visitor arrived on, addressed to the frame's origin only. An ad iframe or an unlisted third-party frame on the same page gets nothing.
- The frame adopts, if it trusts the parent. It stores the parent's journey as its own, resolves every mapped field from it, pushes the
mtk_attributiondataLayer event again with those values, and refills the form. A later visit straight to the form's domain continues that same journey instead of starting a stray one.
Trust is the same rule in both directions: the two hostnames share a registrable domain, or the other hostname is listed under Embedded form domains. Anything else is ignored. The whole bridge can be switched off per license with iframeBridge: false in a page-level config block, though there is rarely a reason to.
Inside the frame, param: sources such as fbclidread the parent page's URL when the frame's own URL lacks the parameter. cookie:sources read the frame's cookies, which a same-site frame shares with its parent anyway.
Verify
Load the main site in a private window with a test URL like ?utm_source=googleads&utm_medium=cpc, open DevTools, and set the console context to the form's frame:
window.mtkBridge
// → { role: "child", origin: "https://www.client.com" } once the frame has adopted the parent's journeyThen the form's hidden inputs carry the parent's values. Back in the top context:
window.mtkBridge
// → { role: "parent", frames: ["https://builder.client.com"] } once it has answered a frameAbsent on bothmeans the frame's request never matched. Check that the tag is on the parent page (an mtk_attribution event in the top window's dataLayer), then that the two hostnames either share a domain or list each other, on both licenses.
Present in the frame, fields still empty is an ordinary field-matching problem: the HTML names or tokens on the embedded form don't match the field map. Same fix as on any platform — see Troubleshooting.
Good to know
- The frame pushes
mtk_attributiontwice: once from its own store at load, once with the parent's values after adopting. A GTM variable reads the newest push. Trigger conversions off the form-submit path, not that page-load event. - If the form's domain is also visited directly (from an ad, or a bookmark), the tag there tracks that visit on its own, as on any site. Embedded visits still adopt the parent's journey, and a direct visit after an embedded one continues it.
- This is for a form domain you control, where the tag can be installed. A hosted builder that won't run the tag (Jotform, Zoho, GoHighLevel iframes) uses that platform's prefill route instead — see its install guide.
- Nothing leaves the browser. The parent hands the journey to one frame it trusts, over the browser's own page-to-page messaging, and the frame writes it to its own storage.