Sign in
Attribution docs
Install guide

Wix

Wix form fields are React-controlled and Wix won't let you set a field's Name, so values written straight to the DOM get reverted. The fix: match fields by their title and add a small helper script that hides them before the page paints and writes through React's value setter.

1

Add the tag to your site

In Settings → Custom Code, add the loader to Head, loaded on all pages, then publish.

<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.

2

Title each field to match the field map

In the Wix form editor, set each field's Field Title to exactly the Field Name it should hold — e.g. First Source or Session Count. The helper script matches on the title, so it must match character-for-character (the HTML Name, e.g. mtk_first_source, works as a title too). Make the fields not required.

Leave every MTK field set to Show.The helper script hides them from visitors — that's its job. Setting a field to Hidden in the Wix form editor removes it from the page entirely, so the script can't fill it and it submits empty. If attribution columns ever go blank, a well-meaning "Hidden" toggle on these fields is the first thing to check.

3

Add the companion Head script

Back in Settings → Custom Code, add the script below to Head, on all pages. It hides every titled MTK field before the browser's first paint(a stylesheet plus a MutationObserver, so React re-renders can't un-hide them and the fields never flash visible), then reads the mtk_attributiondataLayer event and writes each value through React's setter so the form state accepts it. Nothing to configure: the standard Field Names are built in so the hiding applies before first paint, and anything beyond them is picked up from the event, so a field added to the map later works without touching this script.

<!-- MTK Attribution Wix 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;
    }

    // A field answers to its HTML name or its Field Name. Every spelling of
    // the standard fields is seeded here — readable title, raw name, and the
    // retired name the engine still publishes — so the hiding CSS applies
    // before first paint; waiting for the tag's values would let a titled
    // field flash visible. The map is then extended with whatever the event
    // actually carries, so a custom field works without editing this script.
    var LABEL_TO_KEY = {};
    var SEEDED_TITLES = ["First Ad Placement","mtk_first_ad_placement","mtk_ft_placement","mtk_ft_ad_placement","Last Ad Placement","mtk_last_ad_placement","mtk_lt_placement","mtk_lt_ad_placement","First Campaign","mtk_first_campaign","mtk_ft_campaign","Last Campaign","mtk_last_campaign","mtk_lt_campaign","First Channel","mtk_first_channel","mtk_ft_channel","Last Channel","mtk_last_channel","mtk_lt_channel","First Content","mtk_first_content","mtk_ft_content","Last Content","mtk_last_content","mtk_lt_content","First Landing Page","mtk_first_landing_page","mtk_ft_landing","mtk_ft_landing_page","Last Landing Page","mtk_last_landing_page","mtk_lt_landing","mtk_lt_landing_page","First Landing Page Group","mtk_first_landing_page_group","mtk_ft_landing_page_group","Last Landing Page Group","mtk_last_landing_page_group","mtk_lt_landing_page_group","First Medium","mtk_first_medium","mtk_ft_medium","Last Medium","mtk_last_medium","mtk_lt_medium","First Query","mtk_first_query","mtk_ft_landing_query","mtk_ft_query","Last Query","mtk_last_query","mtk_lt_landing_query","mtk_lt_query","First Referrer","mtk_first_referrer","mtk_ft_referrer","Last Referrer","mtk_last_referrer","mtk_lt_referrer","First Source","mtk_first_source","mtk_ft_source","Last Source","mtk_last_source","mtk_lt_source","First Term","mtk_first_term","mtk_ft_term","Last Term","mtk_last_term","mtk_lt_term","gclid","mtk_gclid","mtk_lt_gclid","gbraid","mtk_gbraid","mtk_lt_gbraid","wbraid","mtk_wbraid","mtk_lt_wbraid","msclkid","mtk_msclkid","mtk_lt_msclkid","Journey String","mtk_journey_string","mtk_touch_path","Journey JSON","mtk_journey_json","mtk_touch_path_json","Session Count","mtk_session_count","Pageview Count","mtk_pageview_count","Paid Touch Count","mtk_paid_touch_count","Last Paid Touch","mtk_last_paid_touch","mtk_lt_paid_touch","Time Since Last Paid Touch","mtk_time_since_last_paid_touch","mtk_time_since_paid","Page Path","mtk_page_path","Page URL","mtk_page_url","Time To Conversion","mtk_time_to_conversion","Conversion Unix","mtk_conversion_unix","mtk_conversion_timestamp","Conversion Datetime","mtk_conversion_datetime","Custom Conversion Time","mtk_custom_conversion_time","Tracking Health","mtk_tracking_health","fbclid","mtk_fbclid","fbc","mtk_fbc","fbp","mtk_fbp","Attribution Summary","mtk_attribution_summary","mtk_summary"];
    for (var si = 0; si < SEEDED_TITLES.length; si++) {
      LABEL_TO_KEY[SEEDED_TITLES[si]] = SEEDED_TITLES[si];
    }

    function learn(data) {
      // Matching a form field is not the same job as choosing a payload.
      // keysFrom() drops every retired spelling on purpose — carrying both
      // would double a length-capped URL — but the tag publishes those
      // spellings precisely so older forms keep filling, and a field titled
      // 'mtk_ft_source' is exactly that form. Match on the raw event keys, so
      // a legacy-titled field is both filled and hidden.
      for (var k in data) {
        if (!data.hasOwnProperty(k)) continue;
        if (k.indexOf('mtk_') !== 0) continue;
        LABEL_TO_KEY[k] = k;
      }
      // keysFrom() still decides which fields get a readable title, so the
      // titles stay the current ones rather than 'Ft Source'.
      var keys = keysFrom(data);
      for (var i = 0; i < keys.length; i++) {
        LABEL_TO_KEY[keys[i]] = keys[i];
        LABEL_TO_KEY[RENAME[keys[i]] || titleOf(keys[i])] = keys[i];
      }
    }

    var style = document.createElement('style');
    style.textContent =
      'input[data-mtk-hide], textarea[data-mtk-hide], [data-mtk-hidden] { display: none !important; }';
    (document.head || document.documentElement).appendChild(style);

    function labelOf(el) {
      var l = el.id && document.querySelector('label[for="' + el.id + '"]');
      return ((l && l.textContent) || el.getAttribute('aria-label') || '').trim();
    }

    function wrapperOf(el) {
      var node = el;
      while (node.parentElement) {
        var p = node.parentElement;
        if (p.tagName === 'FORM' || p.querySelectorAll('input, textarea').length > 1) break;
        node = p;
      }
      return node;
    }

    function ensureHidden(el) {
      if (!el.hasAttribute('data-mtk-hide')) el.setAttribute('data-mtk-hide', '1');
      var w = wrapperOf(el);
      if (w !== el && !w.hasAttribute('data-mtk-hidden')) w.setAttribute('data-mtk-hidden', '1');
    }

    function sweep(root) {
      if (!root || !root.querySelectorAll) return;
      var els = root.querySelectorAll('input, textarea');
      for (var i = 0; i < els.length; i++) {
        if (LABEL_TO_KEY[labelOf(els[i])]) ensureHidden(els[i]);
      }
    }

    new MutationObserver(function (muts) {
      for (var i = 0; i < muts.length; i++) {
        var m = muts[i];
        if (m.type === 'childList') {
          for (var j = 0; j < m.addedNodes.length; j++) {
            if (m.addedNodes[j].nodeType === 1) sweep(m.addedNodes[j]);
          }
        } else if (m.target && m.target.nodeType === 1) {
          sweep(m.target.parentElement || m.target);
        }
      }
    }).observe(document.documentElement, {
      childList: true, subtree: true,
      attributes: true, attributeFilter: ['style', 'class', 'aria-label']
    });
    sweep(document);

    function setReactValue(el, value) {
      var proto = el.tagName === 'TEXTAREA' ? HTMLTextAreaElement : HTMLInputElement;
      Object.getOwnPropertyDescriptor(proto.prototype, 'value').set.call(el, value);
      if (el._valueTracker) el._valueTracker.setValue(value === '' ? '\u0000' : '');
      el.dispatchEvent(new Event('input', { bubbles: true }));
      el.dispatchEvent(new Event('change', { bubbles: true }));
    }

    (function tick() {
      var data = (window.dataLayer || []).find(function (e) { return e.event === 'mtk_attribution'; });
      var settled = !!data;
      if (data) learn(data);            // pick up fields beyond the standard set

      document.querySelectorAll('form input, form textarea').forEach(function (el) {
        var key = LABEL_TO_KEY[labelOf(el)];
        if (!key) return;
        ensureHidden(el);
        if (data && data[key] != null) {
          if (el.value !== String(data[key])) setReactValue(el, String(data[key]));
        } else {
          settled = false;
        }
      });

      setTimeout(tick, settled ? 2000 : 150);
    })();
  })();
  </script>
<!-- End MTK Attribution Wix Companion Script -->

Head placement matters: only code inline in the page's <head> runs before first paint. In Body-End (or loaded via GTM) the same script works, but the fields flash visible for a moment before hiding.

Field titles must match exactly.Get the names from Dashboard → the client's license → Tag config → Field map (the Field Name column). More detail in Troubleshooting.