Attributor
Purpose
Emulates old 1st party cookies set for utm_
(source
, medium
, campaign
, term
, content
) + the 3 new source_platform
, marketing_tactic
, creative_format
parameters in Classic Google Analytics (ga.js). Easily fill hidden form fields with the stored values of your first and last touch data (great for closed-loop/ROAS reporting needs). This script is meant to be a standardized means of implementing UTM cookies for campaign attribution.
What data is stored?
1st party cookies attr_first
and attr_last
are stored at root domain .domain.com
.
attr_last
- Has an expiration date of 30-minutes. The 30-minutes is reset on each subsequent page load. This is meant to emulate the same session windows as Google Analytics.
attr_first
- Has an expiration date of 2-years. This cookie will never be set more than once (unless the cookie is of course deleted).
Each of these cookies contains an encoded JSON object of key:value pairs for the following data:
- source
- medium
- campaign
- term
- content
- source_platform
- marketing_tactic
- creative_format
- date
- lp
Most the the data points above are pulled from standard UTM parameters or are given values via referral parsing and/or general metadata (date/lp).
How to Use
Install Attributor.js
This can be done via a custom HTML tag inside of Google Tag Manager - or you can include the script in your own webpack/gulp dependencies.
Creating an Attributor instance
The Attributor
contructor accepts a single configuration object - the library uses sensible defaults, so you only need to override what configuration options you need to change.
var __utmz = new Attributor({
cookieDomain: 'domain.com'
});
Default Field Mapping
Attributor uses sensible defaults for field mapping. Fields are mapped per cookie using HTML <input>
name
attributes. The script can easily be modified to use classes for field mapping. The default field mapping is defined as a simple object literal:
{
first: {
source: 'utm_source_1st',
medium: 'utm_medium_1st',
campaign: 'utm_campaign_1st',
term: 'utm_term_1st',
content: 'utm_content_1st',
source_platform: 'utm_source_platform_1st',
marketing_tactic: 'utm_marketing_tactic_1st',
creative_format: 'utm_creative_format_1st',
adgroup: 'utm_adgroup_1st',
lp: 'lp_1st',
date: 'date_1st'
},
last: {
source: 'utm_source',
medium: 'utm_medium',
campaign: 'utm_campaign',
term: 'utm_term',
content: 'utm_content',
source_platform: 'utm_source_platform',
marketing_tactic: 'utm_marketing_tactic',
creative_format: 'utm_creative_format',
adgroup: 'utm_adgroup',
lp: 'lp_last',
date: 'date_last'
},
cookies: {
_fbc: 'fbc', // Facebook Ads Click ID
_fbp: 'fbp', // Facebook Ads Browser ID
_ga: 'ga', // Google Analytics Client ID
_gcl_aw: 'gclid', // Google Ads Click ID
_uetmsclkid: 'msclkid', // Bing/Microsoft Ads Click ID
li_fat_id: 'li_fat_id', // LinkedIn Click ID
ttclid: 'ttclid' // TikTok Ads Click ID
},
globals: {
'navigator.user_agent': 'user_agent',
'document.referrer': 'referrer',
'location.href': 'conversion_url'
}
}
This means that if you were to use the following hidden field markup - you need not configure any custom field mapping object:
<!-- last -->
<input type="hidden" name="utm_source">
<input type="hidden" name="utm_medium">
<input type="hidden" name="utm_campaign">
<input type="hidden" name="utm_term">
<input type="hidden" name="utm_content">
<input type="hidden" name="utm_source_platform">
<input type="hidden" name="utm_marketing_tactic">
<input type="hidden" name="utm_creative_format">
<input type="hidden" name="utm_adgroup">
<input type="hidden" name="lp_last">
<input type="hidden" name="date_last">
<!-- first -->
<input type="hidden" name="utm_source_1st">
<input type="hidden" name="utm_medium_1st">
<input type="hidden" name="utm_campaign_1st">
<input type="hidden" name="utm_term_1st">
<input type="hidden" name="utm_content_1st">
<input type="hidden" name="utm_source_platform_1st">
<input type="hidden" name="utm_marketing_tactic_1st">
<input type="hidden" name="utm_creative_format_1st">
<input type="hidden" name="utm_adgroup_1st">
<input type="hidden" name="lp_1st">
<input type="hidden" name="date_1st">
<!-- cookies -->
<input type="hidden" name="ga">
<input type="hidden" name="gclid">
<input type="hidden" name="fbp">
<input type="hidden" name="fbc">
<input type="hidden" name="msclkid">
<input type="hidden" name="li_fat_id">
<input type="hidden" name="ttclid">
<!-- globals -->
<input type="hidden" name="user_agent">
<input type="hidden" name="referrer">
<input type="hidden" name="conversion_url">
Defining a custom field map
If you cannot (or wish not to) use the default field mapping. You can easily create your own by defining you own fieldMap
in the configuration object passed to the Attributor
constructor. Custom field mappings are merged into the default fieldMap
object.
var __utmz = new Attributor(
cookieDomain: 'domain.com',
fieldMap: {
first: {
source: 'source_1st',
medium: 'medium_1st',
campaign: 'campaign_1st',
term: 'term_1st',
content: 'content_1st',
},
last: {
source: 'source_last',
medium: 'medium_last',
campaign: 'campaign_last',
term: 'term_last',
content: 'content_last',
}
}
});
If you only need/wish to change a few of the default field mappings - you can set those explicitly without affecting the other defaults:
var __utmz = new Attributor({
cookieDomain: 'mydomain.com',
fieldMap: {
cookies: {
_ga: 'ga_client_id'
}
}
});
Filters
The library has some predefined filters to handle filtering the cookie and global values. Note - these filters only work on the cookies
and globals
. You can define filters using the filters
property in the configuration object. For example the following filter will remove the first two delimited values from the _ga
cookie. The filter property name must match the name of the cookie.
filters: {
_ga: function(val) {
// e.g: GA1.2.1234567890.0987654321
// Should return 1234567890.0987654321
return val.split('.').slice(2).join('.');
}
}
Prefilling
The script will automatically run it's Attributor.fillFormFields()
method when initialized.
You can also manually call the method as such:
var __utmz = new Attributor(config);
__utmz.fillFormFields();
This can be helpful for a few scenarios
- Forms loaded via JS or 3rd party systems
- When a form is loaded into the DOM after initial pageload (e.g: a modal/popup)
HubSpot
window.addEventListener('message', function(event) {
if (event.data.type === 'hsFormCallback' && event.data.eventName === 'onFormReady') {
__utmz.fillFormFields();
}
});
Marketo
if (typeof MktoForms2 !== 'undefined') {
MktoForms2.whenReady( function( form ) {
__utmz.fillFormFields();
});
}
Changing the Prefill Target Method
By default, Attributor will use <input>
name
attributes for updating/prefilling. This can be customized when initializing attributor using the fieldTargetMethod
property.
name
(default) - usesdocument.getElementsByName('{field_map_key}')
class
- usesdocument.querySelectorAll('input.{field_map_key}')
parentClass
- usesdocument.querySelectorAll('.{field_map_key} input')
For example:
// JavaScript
var __utmz = new Attributor({
cookieDomain: 'mydomain.com',
fieldTargetMethod: 'class',
fieldMap: {
last: {
source: 'custom_source_last'
}
}
});
<!-- HTML -->
<input type="hidden" name="input_9a8dfs" class="custom_source_last">