Track Typeform Submissions
Typeform embeds run in an iframe — GTM cannot see anything inside it. The Embed SDK exposes a small set of callbacks, plus a postMessage API for raw iframe embeds. This recipe wires both paths and captures the responseId so you can link each GA4 event back to the Typeform response in the admin dashboard.
Valid as of April 2026, Typeform Embed SDK v3.
The success signal
Section titled “The success signal”Typeform emits two relevant signals:
- Embed SDK
onSubmitcallback — fires the moment the user reaches the end screen after answering the last question. Passes{ formId, responseId }. - postMessage
form-submit— equivalent toonSubmit, fires on the parent window from the iframe. Use this for raw iframe embeds where you do not use the SDK.
A related event form-ready fires when the embed loads, not on submission — do not use it for conversions.
dataLayer push pattern
Section titled “dataLayer push pattern”Typeform Embed SDK — createWidget, createPopup, or createSlider all accept onSubmit.
<div id="typeform-inline" data-tf-live="01ABCDEFGHIJKLMNOPQRSTUVWX"></div><script src="https://embed.typeform.com/next/embed.js"></script><script> // The inline data-tf-live attribute auto-initialises. For programmatic control: (function () { if (!window.tf || !window.tf.createWidget) return;
window.tf.createWidget('01ABCDEFGHIJKLMNOPQRSTUVWX', { container: document.getElementById('typeform-inline'), onSubmit: function (event) { window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: 'form_submission', form_vendor: 'typeform', typeform_id: event.formId, typeform_response_id: event.responseId }); } }); })();</script>For popup or slider embeds the SDK signature is the same — tf.createPopup(formId, { onSubmit }) and tf.createSlider(formId, { onSubmit }).
For raw iframe embeds (no SDK):
window.addEventListener('message', function (event) { if (!event.origin.endsWith('.typeform.com')) return; if (!event.data || event.data.type !== 'form-submit') return;
window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: 'form_submission', form_vendor: 'typeform', typeform_id: event.data.formId, typeform_response_id: event.data.responseId });});GTM setup
Section titled “GTM setup”-
Create a Custom Event trigger
- Trigger type: Custom Event
- Event name:
form_submission - This fires: Some Custom Events →
DLV - form_vendorequalstypeform
-
Create Data Layer Variables
DLV - form_vendor→form_vendorDLV - typeform_id→typeform_idDLV - typeform_response_id→typeform_response_id
-
Create a GA4 Event Tag
- Event name:
generate_lead - Parameters:
form_vendor→{{DLV - form_vendor}}typeform_id→{{DLV - typeform_id}}typeform_response_id→{{DLV - typeform_response_id}}
- Trigger: the Custom Event trigger above
- Event name:
-
Test in Preview mode
Complete a Typeform submission end-to-end. Confirm
form_submissionappears with both IDs.
GA4 - generate_lead (Typeform)
- Type
- Google Analytics: GA4 Event
- Trigger
- Custom Event - form_submission (typeform)
- Variables
-
DLV - form_vendorDLV - typeform_idDLV - typeform_response_id
Linking back to Typeform responses
Section titled “Linking back to Typeform responses”Every Typeform response has a unique responseId. You can open it directly in the Typeform admin:
https://admin.typeform.com/form/<typeform_id>/results#responses/<response_id>In GA4, register typeform_response_id as a custom dimension so you can surface it in reports. Combine with BigQuery export to join GA4 session data against Typeform’s own Responses API (which accepts response_id as a filter) — useful for attribution without shipping PII to GA4.
Tracking progress through questions (optional)
Section titled “Tracking progress through questions (optional)”The SDK also exposes onQuestionChanged (fires when the user advances between questions) and onEndingButtonClick (fires on custom end-screen CTA clicks):
window.tf.createWidget('01ABCDEFGHIJKLMNOPQRSTUVWX', { container: document.getElementById('typeform-inline'), onQuestionChanged: function (event) { window.dataLayer.push({ event: 'typeform_question_changed', typeform_id: event.formId, typeform_question_ref: event.ref }); }, onSubmit: function (event) { /* as above */ }});Create a separate Custom Event trigger for typeform_question_changed if you want funnel-style reporting.
Test it
Section titled “Test it”- Open GTM Preview mode on a page with a Typeform embed.
- Complete the Typeform end-to-end — answer all questions and reach the end screen.
- Confirm
form_submissionappears in the Summary pane withform_vendor: typeform, a validtypeform_id, and a populatedtypeform_response_id. - Verify Tags Fired lists the GA4 tag.
- In GA4 DebugView, confirm
generate_leadarrives with both IDs. - Open the Typeform admin and confirm the
typeform_response_idyou captured matches a real response in the dashboard. - Test abandon path: start the Typeform but close it before reaching the end screen. Confirm no
form_submissionfires.
Common gotchas
Section titled “Common gotchas”onSubmit fires on end-screen reach, not on explicit close. If your Typeform has no end screen (legacy setups), onSubmit may not fire. Add an end screen — even a minimal one.
Popup embeds require user action to close. The submission is counted on end-screen reach regardless of whether the user clicks the close button.
Autoclose settings. If you use Typeform’s autoclose-on-submit feature, onSubmit fires immediately before the close animation — your dataLayer push is already queued, so this is fine.
Cross-origin postMessage noise. Many iframes on the page post messages to the parent. Always filter by event.origin.endsWith('.typeform.com') before trusting the payload.
Typeform CDN cache for embed.js. If the CDN serves a stale SDK (browser cache), tf.createWidget may be undefined. Add a ?v=3 query string or a defensive if (!window.tf || !window.tf.createWidget) check.
Multiple Typeforms on one page. The SDK fires onSubmit per widget instance with its own formId. No disambiguation logic needed — each listener is bound to its own widget.