Many form platforms (Typeform, Tally, Paperform, JotForm, etc.) integrate forms into websites via iframes or embed scripts. This creates a conversion tracking challenge: since the iframe runs on a different domain, the GTM container on the parent page cannot directly capture events inside the iframe.
This guide covers how to capture iframe form events using the window.postMessage API and push them to GTM. The approach is platform-agnostic and works with any iframe form.
The Problem: iframe and Cross-domain Boundaries
When a form is embedded via iframe:
- The iframe runs on a different origin (e.g.,
tally.so,typeform.com,paperform.co) - The parent page cannot access the iframe’s DOM (same-origin policy)
- Cookies inside the iframe are subject to third-party cookie restrictions (Safari, Firefox)
- The GTM container only runs on the parent page and cannot see events inside the iframe
The Solution: window.postMessage
The window.postMessage API enables secure message passing between window objects on different origins. When a form platform triggers an event inside the iframe (form submission, page change, etc.), it sends a message to the parent page. A listener on the parent page captures this message and pushes it to GTM via dataLayer.push().
General Flow
iframe (form platform)
↓ form submitted
↓ window.parent.postMessage({ event: "formSubmitted" }, "*")
↓
parent page
↓ window.addEventListener("message", handler)
↓ origin check
↓ dataLayer.push({ event: "form_submitted" })
↓
GTM
↓ Custom Event trigger: "form_submitted"
↓ GA4 Event / Google Ads Conversion / other tags
Platform-specific postMessage Support
Some form platforms automatically send postMessage events:
Tally: Automatically sends Tally.FormSubmitted, Tally.FormLoaded, Tally.FormPageView events. See the Tally Conversion Tracking Guide for details.
Typeform: Sends TypeformSubmit and TypeformQuestionPassed events via dataLayer. See the Typeform Conversion Tracking Guide for details.
Paperform: Sends SubmittedForm on form submission, StartedCheckout on payment initiation, StartedSubmission when the user starts filling out the form.
For platforms without automatic postMessage support, you need to manually send messages from inside the iframe.
Method 1: Parent Page Access Available
If you have access to the parent page’s code, you can add a listener directly.
Step 1: Sending Messages from Inside the iframe
Add this code to the form platform’s “post-submission script” field (if available):
<script>
window.parent.postMessage(
{
message: "form_submitted",
},
"<parent-page-hostname>",
);
</script>
<parent-page-hostname> is the origin of the site where the form is embedded (e.g., https://example.com).
Step 2: Listener on the Parent Page
On the parent page, listen for messages from the iframe and push to GTM:
window.addEventListener("message", function (event) {
// Origin check: only accept messages from trusted sources
if (
event.origin === "https://form-platform.com" &&
event.data.message === "form_submitted"
) {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: "iframe_form_submitted",
});
}
});
Origin checking is critical for security. Always verify event.origin.
Accepting messages from any origin creates an XSS risk.
Step 3: GTM Trigger and Tag
In GTM:
- Trigger: Custom Event with event name
iframe_form_submitted - Tag: GA4 Event, Google Ads Conversion, or your desired platform tag
Method 2: GTM Access Only
If you don’t have direct access to the parent page’s code, you can run the same listener inside a GTM Custom HTML tag.
Create a new Custom HTML tag in your GTM container:
<script>
(function () {
try {
if (typeof window.addEventListener !== "undefined") {
window.addEventListener("message", function (event) {
// Specify trusted form platform origins
var trustedOrigins = [
"https://tally.so",
"https://form.typeform.com",
"https://your-form-id.paperform.co",
];
if (trustedOrigins.indexOf(event.origin) !== -1) {
// Platform-specific event detection
var eventName = null;
// Tally
if (event.data && event.data.event === "Tally.FormSubmitted") {
eventName = "tally_form_submitted";
}
// Typeform
else if (event.data && event.data.event === "TypeformSubmit") {
eventName = "typeform_form_submitted";
}
// Generic postMessage
else if (event.data && event.data.message === "form_submitted") {
eventName = "iframe_form_submitted";
}
if (eventName) {
window.dataLayer.push({ event: eventName });
}
}
});
}
} catch (err) {
console.log(err);
}
})();
</script>
Fire this tag with an All Pages trigger. This keeps the listener active on every page, capturing form events from any iframe.
Paperform Specific Case
Paperform uses the following structure for embedded forms:
<div data-paperform-id="<form-id>"></div>
<script>
(function () {
var script = document.createElement("script");
script.src = "https://paperform.co/__embed";
document.body.appendChild(script);
})();
</script>
Paperform provides Google Analytics ID and Facebook Pixel ID fields under Configure > Analytics. However, no direct dataLayer push is made for GTM. The postMessage method is therefore required.
Add this code to the Successful submission scripts field:
<script>
window.parent.postMessage(
{
message: "form_submitted",
},
"https://your-site.com",
);
</script>
Cross-domain Considerations
Key points when tracking iframe forms:
- Origin checking: Always verify
event.origin - Third-party cookies: Safari and Firefox may block cookies inside iframes. In this case, GA4/GTM won’t work inside the iframe, making the postMessage approach more reliable
- Referral exclusion: Add the form platform’s domain to your GA4 referral exclusion list
- Consent: Consider GDPR/ePrivacy requirements. Don’t fire tracking code without proper consent
For cross-domain tracking details, see Google Analytics and Google Tag Manager Domain Management.
Testing and Verification
- In the browser console, use
window.addEventListener('message', (e) => console.log(e.origin, e.data))to monitor incoming messages - Submit a form in GTM Preview mode and verify the event appears in the dataLayer
- Confirm that associated tags fire correctly