Squarespace
Squarespace Form Blocks build their submission from internal state, so values written into the page's inputs never reach the submission. What does work is Squarespace's own prefill: a Hidden field is populated from a URL parameter named SQF_+ the field's name, uppercased. So the install is two scripts: the tag, plus a small companion that copies the attribution values into those parameters (one quick reload) and lets Squarespace fill the fields itself.
Add the tag to your site
In Squarespace, open Settings → Advanced → Code Injection (on newer plans: Settings → Developer Tools → Code Injection), paste the loader into Header, and save.
<script src="https://attr.boggsmtk.com/v1/tag.js?k=YOUR_LICENSE_KEY"></script>
Swap YOUR_LICENSE_KEYfor the client's key from the dashboard. Code Injection requires a plan with code injection enabled (Business or higher).
Add the companion script
On the same Code Injection screen, paste this into Footerand save. Nothing to configure: it prefills whichever fields you add, from whatever the tag published, so a field the platform gains later needs no edit here. Every value becomes a URL parameter before the reload, so if you ever see its console warning about the URL length, list the ones you don't need in EXCLUDE.
<!-- MTK Attribution Squarespace 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;
}
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;
}
var tries = 0;
function fill() {
var data = attributionData();
if (!data || !document.querySelector('form')) {
if (tries++ < 1200) return window.requestAnimationFrame(fill);
return;
}
var params = new URLSearchParams(window.location.search);
var keys = keysFrom(data);
var changed = false;
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (data[key] === undefined) continue;
// Squarespace prefills a Hidden field from SQF_ + its Field Name,
// uppercased with spaces as underscores.
var sqf = 'SQF_' + (RENAME[key] || titleOf(key)).toUpperCase().replace(/ /g, '_');
if (params.has(sqf)) continue;
params.set(sqf, data[key] === null ? '' : String(data[key]));
changed = true;
}
// Every value becomes a URL parameter and the page reloads with them, so
// a long map is the thing that breaks this: browsers give up past a few
// thousand characters. Trim with EXCLUDE if this ever gets close.
if (changed && ('?' + params.toString()).length > 4000 && window.console && console.warn) {
console.warn(
'[MTK Squarespace] the prefill URL is ' + ('?' + params.toString()).length +
' characters; drop fields you do not need into EXCLUDE.'
);
}
if (changed) window.location.search = '?' + params.toString();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', fill);
} else {
fill();
}
})();
</script>
<!-- End MTK Attribution Squarespace Companion Script -->It waits for the tag's data, writes each value into an SQF_… URL parameter, and reloads the page once so Squarespace prefills the hidden fields. Pages without a form are left alone, and the reload only ever happens once per page load.
Using GTM instead? The companion is ES5-safe, so it can also run as its own Custom HTML tag firing on all pages (or pasted below the loader in the same tag) — it just has to load on the Squarespace pages one way or the other.
Edit Form Fields and add a Hidden field
Edit the page with your form, click the Form Block, then open Edit Form Fields and add a Hidden field. Name it exactly the Field Name from your field map (e.g. Last Channel) — that name is how Squarespace connects the field to the companion's URL parameter (Last Channel → SQF_LAST_CHANNEL), so it has to match character-for-character. Leave the field's value empty — Squarespace fills it from the URL parameter. Don't paste [mtk:…] tokens as the value: Squarespace never replaces them, and any visitor whose ad blocker stops the tag submits the literal token text into your lead record. An empty field fails clean.
Repeat for each field, then save
Add one hidden field per value you want to capture, each named to its Field Name exactly as the field map shows it — that name is what the companion turns into the SQF_ parameter Squarespace prefills from. Then save the form and the page.
?utm_source=googleads&utm_medium=cpc, submit, and check the values in the form's storage or notification email.params.has() guard means it never loops. Analytics may count one extra pageview on form pages.Attribution Summary— its value runs past Squarespace's ~240-character field limit, so submits fail validation (“Review the following information: Attribution Summary”). And Journey JSON, which the companion leaves out to keep URLs short. Everything they contain already arrives through the individual fields.SQF_ parameters, and two prefill scripts competing over one form conflict with each other and double the extra pageviews.SQF_ parameters and no mtk_attribution event in window.dataLayermeans the tag itself was blocked (usually an ad blocker); parameters missing while the event exists means the companion is missing or outdated — re-paste the current script from this page, since older versions checked for the form only once and could race Squarespace's form rendering; parameters present but one field empty means that field's name doesn't match its Field Name exactly.