iFrame embed
The iFrame embed points an <iframe> straight at the form on form.jotform.comand adds Jotform's handler script to keep the frame's height in step with the form. A cross-origin iframe can't be read from the parent page, so the tag can't reach these fields. What your page doescontrol is the iframe's src, and Jotform prefills any field whose Unique Name matches a query parameter on it. The companion script below writes those parameters in before the frame loads.
Add the companion script
Put it in the page's <head>, directly below the tag loader — or as its own GTM Custom HTML tag firing on all pages. It's the same script the Standard embed uses; one copy covers both if a site has each. Nothing to configure: it forwards every value on the tag's dataLayer event, so a field added to the field map later arrives here on its own.
<!-- MTK Attribution Jotform iframe Companion Script -->
<script>
(function () {
// Values to keep OUT. Both of these are long enough to crowd a URL past
// its ceiling on their own, and everything inside them already rides
// along in the individual fields. Add any field you don't want on the
// platform — each one you drop frees room for the rest.
var EXCLUDE = [
'mtk_journey_json',
'mtk_attribution_summary'
];
// Only when the platform's key has to differ from the field name.
var RENAME = {
// 'mtk_first_channel': 'first_channel'
};
// The tag also publishes each value under its retired spelling so older
// client forms keep filling. That is for forms, not for us — carrying both
// would double the payload for nothing.
var LEGACY_PREFIXES = ['mtk_ft_', 'mtk_lt_'];
var LEGACY_NAMES = [
'mtk_time_since_paid',
'mtk_conversion_timestamp',
'mtk_summary',
'mtk_touch_path',
'mtk_touch_path_json'
];
// Titles the generic rule can't produce (click IDs stay lowercase).
var TITLE_EXCEPTIONS = {"mtk_gclid":"gclid","mtk_gbraid":"gbraid","mtk_wbraid":"wbraid","mtk_msclkid":"msclkid","mtk_page_url":"Page URL","mtk_fbclid":"fbclid","mtk_fbc":"fbc","mtk_fbp":"fbp"};
function isLegacy(key) {
for (var i = 0; i < LEGACY_PREFIXES.length; i++) {
if (key.indexOf(LEGACY_PREFIXES[i]) === 0) return true;
}
return LEGACY_NAMES.indexOf(key) > -1;
}
// 'mtk_first_channel' -> 'First Channel', matching the Field Name the
// dashboard shows for that row.
function titleOf(key) {
if (TITLE_EXCEPTIONS[key]) return TITLE_EXCEPTIONS[key];
var words = key.replace(/^mtk_/, '').split('_');
var out = [];
for (var i = 0; i < words.length; i++) {
if (!words[i]) continue;
out.push(
words[i] === 'json'
? 'JSON'
: words[i].charAt(0).toUpperCase() + words[i].slice(1)
);
}
return out.join(' ');
}
// Order values are added in, which on a length-capped platform is also
// drop priority: what a lead is worth least without goes last. Anything
// not listed (a custom field, a new default) sorts after these, so a drop
// is predictable rather than whatever order the event happened to be in.
var PRIORITY = [
'mtk_last_channel', 'mtk_last_source', 'mtk_last_medium', 'mtk_last_campaign',
'mtk_gclid', 'mtk_gbraid', 'mtk_wbraid', 'mtk_msclkid',
'mtk_first_channel', 'mtk_first_source', 'mtk_first_medium', 'mtk_first_campaign',
'mtk_page_path', 'mtk_page_url',
'mtk_conversion_unix', 'mtk_conversion_datetime', 'mtk_custom_conversion_time',
'mtk_fbclid', 'mtk_fbc', 'mtk_fbp',
'mtk_last_landing_page', 'mtk_last_landing_page_group', 'mtk_first_landing_page',
'mtk_session_count', 'mtk_pageview_count', 'mtk_paid_touch_count',
'mtk_last_paid_touch', 'mtk_time_since_last_paid_touch', 'mtk_time_to_conversion',
'mtk_journey_string'
];
// Every mtk_* value the event carries, minus the excluded and the retired.
function keysFrom(data) {
var out = [];
for (var key in data) {
if (!data.hasOwnProperty(key)) continue;
if (key.indexOf('mtk_') !== 0) continue;
if (isLegacy(key)) continue;
if (EXCLUDE.indexOf(key) > -1) continue;
out.push(key);
}
out.sort(function (a, b) {
var ia = PRIORITY.indexOf(a), ib = PRIORITY.indexOf(b);
if (ia === -1 && ib === -1) return a < b ? -1 : a > b ? 1 : 0;
if (ia === -1) return 1;
if (ib === -1) return -1;
return ia - ib;
});
return out;
}
var EXTRA_HOSTS = [];
var HOST_RE = /(^|\.)(jotform\.(com|us|ca|eu|io|pro)|jotformeu\.com|jotfor\.ms)$/i;
var PATH_RE = /^\/(jsform\/)?\d{6,}(\/|$|\?)/;
var URL_ATTRS = ['src', 'data-src', 'data-lazy-src', 'data-litespeed-src'];
var MAX_WAIT_MS = 3000;
var MAX_URL_LEN = 1800;
var pending = [];
var flushed = false;
function attributionData() {
var dl = window.dataLayer || [];
for (var i = dl.length - 1; i >= 0; i--) {
if (dl[i] && dl[i].event === 'mtk_attribution') return dl[i];
}
return null;
}
function isFormUrl(url) {
var m = /^(?:https?:)?\/\/([^\/?#]+)([^?#]*)/.exec(url);
if (!m) return false;
var host = m[1].replace(/:\d+$/, '');
var path = m[2] || '/';
if (EXTRA_HOSTS.indexOf(host) > -1) return PATH_RE.test(path);
return HOST_RE.test(host) && PATH_RE.test(path);
}
function formUrl(el) {
if (!el || el.tagName !== 'IFRAME') return null;
for (var i = 0; i < URL_ATTRS.length; i++) {
var value = el.getAttribute(URL_ATTRS[i]);
if (value && isFormUrl(value)) return { attr: URL_ATTRS[i], url: value };
}
return null;
}
function withAttribution(url, data) {
url = url.replace('/jsform/', '/');
if (!data) return url;
var keys = keysFrom(data);
var sep = url.indexOf('?') > -1 ? '&' : '?';
var qs = '';
var dropped = [];
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var value = data[key];
if (value === undefined || value === null || value === '') continue;
var pair = encodeURIComponent(RENAME[key] || key) + '=' + encodeURIComponent(String(value));
if (url.length + sep.length + qs.length + pair.length + 1 > MAX_URL_LEN) {
dropped.push(key);
continue;
}
qs += (qs ? '&' : '') + pair;
}
if (dropped.length && window.console && console.warn) {
console.warn(
'[MTK Jotform] form URL hit its ' + MAX_URL_LEN + '-character ceiling; ' +
'these did not fit: ' + dropped.join(', ')
);
}
return qs ? url + sep + qs : url;
}
function done(el, attr, url) {
el.setAttribute('data-mtk-done', '1');
el.setAttribute(attr, withAttribution(url, attributionData()));
}
function resolve(el) {
var parkedUrl = el.getAttribute('data-mtk-src');
if (parkedUrl) return done(el, 'src', parkedUrl);
var found = formUrl(el);
if (found) done(el, found.attr, found.url);
}
function consider(el) {
if (!el || el.nodeType !== 1) return;
if (el.getAttribute && el.getAttribute('data-mtk-done')) return;
var found = formUrl(el);
if (!found) return;
if (found.attr !== 'src') {
if (flushed) resolve(el);
else if (pending.indexOf(el) === -1) pending.push(el);
return;
}
if (flushed) return resolve(el);
el.setAttribute('data-mtk-src', found.url);
el.removeAttribute('src');
if (pending.indexOf(el) === -1) pending.push(el);
}
function flush() {
if (flushed) return;
flushed = true;
for (var i = 0; i < pending.length; i++) resolve(pending[i]);
pending = [];
}
function scan(node) {
if (!node || node.nodeType !== 1) return;
consider(node);
if (!node.querySelectorAll) return;
var frames = node.querySelectorAll('iframe');
for (var i = 0; i < frames.length; i++) consider(frames[i]);
}
new MutationObserver(function (muts) {
for (var i = 0; i < muts.length; i++) {
var m = muts[i];
if (m.type === 'attributes') consider(m.target);
else for (var j = 0; j < m.addedNodes.length; j++) scan(m.addedNodes[j]);
}
}).observe(document.documentElement, {
childList: true, subtree: true,
attributes: true, attributeFilter: URL_ATTRS
});
scan(document.documentElement);
(function wait(start) {
if (attributionData()) return flush();
if (Date.now() - start > MAX_WAIT_MS) return flush();
setTimeout(function () { wait(start); }, 50);
})(Date.now());
})();
</script>
<!-- End MTK Attribution Jotform iframe Companion Script -->Because the iFrame embed ships with src already set, the browser would otherwise start fetching the untagged URL as it parses. The companion stashes that URL and drops src for the moment it takes the values to land, then puts it back with the parameters on — so the form loads once, already filled, with no reload to wipe anything.
Paste the embed code — unchanged
In the form builder, go to Publish → Embed, choose iFrame, and click Copy Code. Paste it on your page where the form should appear. It looks like this:
<iframe
id="JotFormIFrame-241076660384054"
title="Contact Us"
onload="window.parent.scrollTo(0,0)"
allow="geolocation; microphone; camera; fullscreen"
src="https://form.jotform.com/241076660384054"
frameborder="0"
style="min-width:100%;max-width:100%;height:539px;border:none;"
scrolling="no"
></iframe>
<script src="https://cdn.jotfor.ms/s/umd/latest/for-form-embed-handler.js"></script>
<script>window.jotformEmbedHandler("iframe[id='JotFormIFrame-241076660384054']", "https://form.jotform.com/")</script>Nothing in it needs editing — the companion finds the frame by its Jotform form URL, not by idor any marker attribute. Keep the handler script that comes with it; that's what resizes the frame, and the companion doesn't interfere with it.
Publish and verify
Publish, then load the page in a private window with a test URL like ?utm_source=googleads&utm_medium=cpc, submit the form, and check the entry under Submissions in Jotform.
To check it without submitting, run this in the console with the context set to top, not the form's frame:
document.querySelectorAll('iframe[data-mtk-done]').forEach(function (f) {
console.log(f.getAttribute('data-src') || f.getAttribute('src'));
});The companion stamps data-mtk-doneon every frame it tags, so a count of zero means it never recognized the iframe. Otherwise you'll see the URL it built. Open that URL in its own tab — as a top-level page it's no longer cross-origin, so you can inspect the form's inputs and confirm each value landed in the field you expected.
1. Did the companion tag the frame? Zero
data-mtk-doneiframes means it never matched the URL. An Enterprise or white-label form domain won't match the built-in host list — add it to EXTRA_HOSTS at the top of the script.2. Are the parameters on the URL but the fields still empty? That's a Unique Name mismatch. Re-open the field in the builder and compare it character-for-character against the HTML Name; if Jotform rewrote it on save, put the mapping in
RENAME.3. Is the field actually on the published form?Open the tagged URL in its own tab and count the inputs against the builder. A field in the builder with no counterpart on the served form means the form wasn't saved.
4. Is the tag itself loading? No
mtk_attribution event in window.dataLayer means it was blocked, usually by an ad blocker — the companion then releases the form unchanged, which is why it still renders.Still stuck? Troubleshooting.
about:blank: WP Rocket, Perfmatters, LiteSpeed and similar rewrite an embed to src="about:blank" and park the real URL in data-src, swapping it back in on scroll. The companion reads those attributes as well as src and tags whichever one holds the form URL, so the lazy swap loads an already-tagged URL with no reload — no need to exclude the iframe from lazy loading.https://form.jotform.com/241076660384054?mtk_last_source=google&mtk_last_medium=cpc
Journey JSON and Attribution Summary. Both are long enough to crowd the form URL past its practical ceiling on their own, so they ship in the companion's EXCLUDE list — everything they contain already arrives through the individual fields. Need those two? Use the Source Code embed instead.