SOLVED

Re: Cookie based UTM tracking

Go to solution
KyleacQuire
Level 2

Hello,

 

How do I set up cookie-based UTM tracking in Marketo? I want to be able to track source even if a person navigates away from the URL with the UTM parameters.

 

Is it possible to track, if UTM URL parameters not in URL, set cookie values, and if UTM URL parameters is present, set those.

Tags (2)
1 ACCEPTED SOLUTION
SanfordWhiteman
Level 10 - Community Moderator

There are a number of JS libraries for this purpose, from the rudimentary to the robust to the ridiculously buggy. ConversionPath and the Digital Pi tracker are two you can trust.

 

But I’d suggest something even simpler: a UTM Forwarder (or more generally, a Query Param Forwarder) which simply picks up the UTM params and forwards them from page to page — unless the pageview originally had its own UTMs, of course. Then you continue to Auto-Fill from the query string.

 

You can place this code on every page, making sure it comes before the Forms 2.0 library on those pages that have forms:

 

/**
 * Simple Same-Origin Query Param Forwarder
 * @author Sanford Whiteman, TEKNKL (blog.teknkl.com / sandy@teknkl.com)
 * @version v2.0.1
 * @copyright Copyright 2024 FigureOne, Inc.
 * @license MIT License: You must include this license and the above credits in all uses & reproductions of this software.
 */
function forwardParams(userOpts){
   let defaultOpts = {
      storageArea: "local",
      storageKey: "last_utmified_query",
      interestingParams: ["utm_campaign", "utm_medium", "utm_source", "utm_content", "utm_term"],
      expireMins: 30,
      restore: true,
      restoreTS: false
   };

   let opts = Object.assign({}, defaultOpts, userOpts);

   let current = {
      state: new URL(document.location.href),
      time: new Date().getTime()
   };

   let storage = {
      area: window[opts.storageArea + "Storage"],
      key: opts.storageKey
   };

   for( [paramName] of [...current.state.searchParams] ) {
      if( !opts.interestingParams.includes(paramName) ) {
         current.state.searchParams.delete(paramName);
      }
   }   

   if( current.state.searchParams.size ) {
      storage.area[storage.key] = JSON.stringify({ 
         time: current.time,
         query: current.state.searchParams.toString()
      });
   } else {
      let restorable = JSON.parse(storage.area[storage.key] || '""');
      if ( !restorable || !opts.restore ) return;

      if ( current.time - restorable.time <= opts.expireMins * 6E4 ) {
         let updated = {
            state: new URL(document.location.href)
         };

         for( [paramName,paramValue] of new URLSearchParams(restorable.query) ) {
            updated.state.searchParams.append(paramName, paramValue);
         }
         
         if(opts.restoreTS) {
            updated.state.searchParams.set("utmified_ts", restorable.time);
         }
         
         history.replaceState("", {}, updated.state.href);
      }
   }
}

forwardParams();

 

 

The last line runs the forwarder function with default options:

  • save to localStorage (note cookies are not used)
  • save and restore the 5 standard utm_* params (utm_campaign, utm_medium, utm_source, utm_content, utm_term)
  • expire the cached params after 30 minutes

 

To change the options, pass an object to forwardParams(). For example, if you want set a shorter expiry for testing, do this instead:

 

forwardParams({
  expireMins: 1
});

 

 

View solution in original post

16 REPLIES 16
SanfordWhiteman
Level 10 - Community Moderator

There are a number of JS libraries for this purpose, from the rudimentary to the robust to the ridiculously buggy. ConversionPath and the Digital Pi tracker are two you can trust.

 

But I’d suggest something even simpler: a UTM Forwarder (or more generally, a Query Param Forwarder) which simply picks up the UTM params and forwards them from page to page — unless the pageview originally had its own UTMs, of course. Then you continue to Auto-Fill from the query string.

 

You can place this code on every page, making sure it comes before the Forms 2.0 library on those pages that have forms:

 

/**
 * Simple Same-Origin Query Param Forwarder
 * @author Sanford Whiteman, TEKNKL (blog.teknkl.com / sandy@teknkl.com)
 * @version v2.0.1
 * @copyright Copyright 2024 FigureOne, Inc.
 * @license MIT License: You must include this license and the above credits in all uses & reproductions of this software.
 */
function forwardParams(userOpts){
   let defaultOpts = {
      storageArea: "local",
      storageKey: "last_utmified_query",
      interestingParams: ["utm_campaign", "utm_medium", "utm_source", "utm_content", "utm_term"],
      expireMins: 30,
      restore: true,
      restoreTS: false
   };

   let opts = Object.assign({}, defaultOpts, userOpts);

   let current = {
      state: new URL(document.location.href),
      time: new Date().getTime()
   };

   let storage = {
      area: window[opts.storageArea + "Storage"],
      key: opts.storageKey
   };

   for( [paramName] of [...current.state.searchParams] ) {
      if( !opts.interestingParams.includes(paramName) ) {
         current.state.searchParams.delete(paramName);
      }
   }   

   if( current.state.searchParams.size ) {
      storage.area[storage.key] = JSON.stringify({ 
         time: current.time,
         query: current.state.searchParams.toString()
      });
   } else {
      let restorable = JSON.parse(storage.area[storage.key] || '""');
      if ( !restorable || !opts.restore ) return;

      if ( current.time - restorable.time <= opts.expireMins * 6E4 ) {
         let updated = {
            state: new URL(document.location.href)
         };

         for( [paramName,paramValue] of new URLSearchParams(restorable.query) ) {
            updated.state.searchParams.append(paramName, paramValue);
         }
         
         if(opts.restoreTS) {
            updated.state.searchParams.set("utmified_ts", restorable.time);
         }
         
         history.replaceState("", {}, updated.state.href);
      }
   }
}

forwardParams();

 

 

The last line runs the forwarder function with default options:

  • save to localStorage (note cookies are not used)
  • save and restore the 5 standard utm_* params (utm_campaign, utm_medium, utm_source, utm_content, utm_term)
  • expire the cached params after 30 minutes

 

To change the options, pass an object to forwardParams(). For example, if you want set a shorter expiry for testing, do this instead:

 

forwardParams({
  expireMins: 1
});

 

 

KyleacQuire
Level 2

Awesome!!!! Thank you.

 

So I need to copy to every page on the website.

Would it work putting it in site wide script in the header?

SanfordWhiteman
Level 10 - Community Moderator

Sure, you can put it in an external .js file which is easier to manage. Just make sure it's on every page, since naturally you need to pick up UTMs wherever they may be.

KyleacQuire
Level 2

@SanfordWhiteman

I've added the code to one of our subdomain websites, sitewide header code, used a UTM link to land on the website, went to an internal page, and submitted a form, but none of the UTM parameters got added to the person record.

Is there any changes needed on the form to accept the different UTMs?

This is no an marketo landing page, its external.

 

For marketo landing pages

how do I link an external JS file in Marketo with landing page templates? Should I copy the code and put it into the template?

SanfordWhiteman
Level 10 - Community Moderator

You'll have to link to your pages(s), nothing to look at without that.

 

You can save the code to a .js file and upload it to Design Studio. Then link to that <script> in the <head>.

KyleacQuire
Level 2

The code has been applied site wide.

I went from home page, with UTM URL, click on case study page, and submitted a market form. But nothing

SanfordWhiteman
Level 10 - Community Moderator

Without having a link to your page I can't do anything else.

KyleacQuire
Level 2

{domain remove}

SanfordWhiteman
Level 10 - Community Moderator

Working as expected for me.

 

(1) Navigated to:

https://gimessentials.acquire.com.au/gim-essentials-transforms-geological-data-management-processes/?utm_medium=my-medium&utm_source=my-source

 

(2) Observed query string persisted to localStorage:

SanfordWhiteman_1-1723182563727.png

 

(3) Within the next few seconds, navigated to:

https://gimessentials.acquire.com.au/

 

(4) Observed the persisted query params immediately restored to current URL:

SanfordWhiteman_2-1723182649246.png

 

(5) Observed the corresponding hidden fields populated on both forms from query params, per Form Editor config:

SanfordWhiteman_3-1723182793152.png

 

 

 

 

KyleacQuire
Level 2

Ahh, I change the hidden fields in the form to capture Cookie parameters, before I had it just on URL parameters so it didn't write. Tested, and working.

 

Thank you so much for that help! Amazing

 

 

SanfordWhiteman
Level 10 - Community Moderator

Not sure I understand your response. The form must fill from query params. Cookies are not used by this code.

 

 

KyleacQuire
Level 2

Yeah ignore that post. I was thinking about cookies again.

 

I have managed to set up the Javascript across a couple of domains and it looks like its working, fantastic. Is it possible to also carry forward UTM's across domains.

For example, a user goes from our primary domain acquire.com.au to market landing page URLs page.acquire.com.au?

SanfordWhiteman
Level 10 - Community Moderator
The code could be expanded to do this, naturally using cookies as that's the only storage mechanism allowed to be used across origins (as long as the origins share a private parent domain, as in your example).
KyleacQuire
Level 2

Do you also have code written for this scenario?

SanfordWhiteman
Level 10 - Community Moderator

Do you also have code written for this scenario?

When falling back on cookies I have a whole other JS library with more features. But this simple forwarder could be enhanced with cookie support. Will add that at some point.

SanfordWhiteman
Level 10 - Community Moderator

@KyleacQuire please return to your thread and check responses.