Collect Code
Working with Einstein Recommendations in Marketing Cloud Engagement.
First published 2020-09-27 in the SFMC Cookbook. Ported here for sfmc.guide.
Official documentation: help.salesforce.com
The default sample codes all assume a page load is happening whenever something needs to be tracked. However, the collect.js library is actually capable of being loaded asynchronously and can be used in a single-page-application (SPA) environment as well, similarly to Google’s analytics.js.
Note to developers
While the offical sample code snippets might lead you to believe that you are looking at an Array, that is in fact only half true. The variable _etmc gets rewritten by collect.js to become an object that happens to also expose a method named push(). Their base snippet in fact assumes that collect.js has been loaded synchronously before you execute your first call to push(). Essentially, the first Array-element is the name of an internal method of collect.js that gets executed.
For more details, have a look at the collect.js library itself.
Initialize the library
Collect Code on page load
Quoting from SFMC’s documentation:
The Collect Code should be placed just before the closing head tag and before any other Marketing Cloud code.
To initialize the tracking, do not forget to replace the two occurences of “INSERT_MID” with your actual BU’s Member ID.
<script src='//INSERT_MID.collect.igodigital.com/collect.js'></script>
This code should be placed directly below the </head> tag following the above tag.
<script>
_etmc.push(['setOrgId', 'INSERT_MID']);
// then execute tracking, e.g.:
// _etmc.push(['trackPageView']);
</script>
Download: Default sample code
Asynchronous Collect Code
The Collect Code tag should be added near the top of the <head> tag and before any other script or CSS tags.
To initialize the tracking, do not forget to replace the two occurences of “INSERT_MID” with your actual BU’s Member ID.
<!-- Einstein Collect Code -->
<script>
(function(e,t,c,n,o,s,a){e[o]=e[o]||[],s=t.createElement(c),a=t.getElementsByTagName(c)[0],s.async=1,s.src=n,a.parentNode.insertBefore(s,a)})(window,document,'script','//INSERT_MID.collect.igodigital.com/collect.js','_etmc');
// always run this line once, followed by what you actually want to track; can be run programmatically in single-page-applications
_etmc.push(['setOrgId', 'INSERT_MID']);
// then execute tracking, e.g.:
// _etmc.push(['trackPageView']);
</script>
<!-- End Einstein Collect Code -->
Download: Async sample code
The above code does four main things:
- Creates a
<script>element that starts asynchronously downloading the collect.js JavaScript library fromhttps://INSERT_MID.collect.igodigital.com/collect.js - Initializes a global
_etmcfunction that allows you to schedule commands to be run once the collect.js library is loaded and ready to go. - Adds a command to the
_etmccommand queue to set the Org ID to your Business Unit Member ID. This is required to ensure your tracking data ends up in the correct Business Unit. - Adds another command to the
_etmccommand queue to track a pageview to Einstein for the current page.
Custom implementations may require modifying the last line of the above code snippet (the trackPageView command) or adding additional code to capture more interactions. However, you should not change the code that loads the collect.js library or initializes the _etmc.push() command queue function. Refer to the official docs for details on available options.
Asynchronous Collect Code with preloading
The above Collect Code snippet will ensure things work across all browsers but it has the disadvantage of not allowing modern browsers to preload the script.
The alternative async tag below adds support for preloading, which will provide a small performance boost on modern browsers, but can degrade to synchronous loading and execution on IE 9 and older mobile browsers that do not recognize the async script attribute. Only use this tag configuration if your visitors primarily use modern browsers to access your site.
To initialize the tracking, do not forget to replace the two occurences of “INSERT_MID” with your actual BU’s Member ID.
<!-- Einstein Collect Code -->
<script>
window._etmc=window._etmc||[];
_etmc.push(['setOrgId', 'INSERT_MID']);
// then execute tracking, e.g.:
// _etmc.push(['trackPageView']);
</script>
<script async src='//INSERT_MID.collect.igodigital.com/collect.js'></script>
<!-- End Einstein Collect Code -->
Keep the above in the <head> section of your code.
Download: Async preload sample code
Debug your tracking solution
Use the following to activate console.log outputs whenever data is send to the server.
// enable debug output
_etmc.debug = true;
Alternatively, look for loaded images in the Network tab of your browser. The calls go to “files” (endpoints) with the name of your method calls, though rewritten to Snake Case.
Tracking and other Collect Code features
There are a lot of options available from the Collect Code library, available via the _etmc.push() method. Only options starting on “track” and “update” actually result in a callout to the server. The “set” and “doNotTrack” methods are mere settings that need to be executed before those.
| Method | Description |
|---|---|
| doNotTrack | Deactivate tracking on the current page. More info below. |
| setFirstParty | Allows you to send tracking data to a server other than the default, e.g. to proxy the data through your own server. Use together with one of the track... methods |
| setInsecure | Use together with setFirstParty to track data via a non-secure proxy server. Only works if the current website was not opened securely either; use together with one of the track... methods |
| setOrgId | set your BUs MID; use together with one of the track... methods |
| setUserInfo | allows to send in an object with user data {email:'', custom:'abc'}; use together with one of the track... methods |
| trackCart | Log items added or removed from a contact’s cart |
| trackConversion | Log details about a contact’s purchase |
| trackEvent | Undocumented feature: Allows you to track custom events |
| trackPageView | Log content/product page views, in-site search terms and category views. More info below |
| trackRating | Log a user’s rating for an item on your website. |
| trackWishlist | Undocumented feature: Allows you to track multiple “shopping carts”-like lists in which users track future wishes |
| updateItem | This allows you to update your product catalog. More info below. |
Disable tracking
Contrary to the official docs, just a single line is needed, which then literally deactivates _etmc.push() and therefore any other tracking calls that are issued afterwards on the current page. This should be executed on page load before other push-calls.
// disable tracking for current page
_etmc.push(['doNotTrack']);
Identify Business Unit for Tracking
This is a required configuration step before tracking anything which allows the collect code to send the tracking data to the right business unit.
_etmc.push(['setOrgId','INSERT_MID']);
Identify current user
Contrary to what the official docs state, the only line required to define the user is this:
_etmc.push(['setUserInfo', {'email': 'INSERT_EMAIL_OR_UNIQUE_ID'}]);
// run a generic trackPageView once to set cookies that are necessary for personalized Web Recommendations to show up
_etmc.push(['trackPageView']);
According to a well hidden part of the documentation Einstein Engagement Scoring actually supports for INSERT_EMAIL_OR_UNIQUE_ID:
- Subscriber Key, which can be implemented with a support request (not tested yet)
- Subscriber ID
- Email address
- MD5 Hashed lowercase version of email address
… as your subscriber identifier in Collect Tracking Code. Based on the source of collect.js, this should always be handed in as a value of 'email'.
Thinking about using Einstein in Journey Builder it makes sense to align with something that Einstein can actually understand and map to existing contacts in SFMC. However, there is the automatically created attribute group that links the PI_* Data Extensions to a Contact using the Email. Based on what I was able to find out, one should simply ignore that Attribute Group altogether.
On the other hand, if all you care about is showing Einstein powered recommendations, you simply have to ensure that you use the same string when you retrieve web/email recommendations that you previously used for tracking via collect code.
Attribute Affinity
This should theoretically boost catalog items that carry the same attribute (detail-field and value) defined as the current user.
Quote: Match a contact attribute to a tagged catalog field to increase the subscriber’s affinity for the value of that contact attribute. The amount of increase is less than what results from a purchase but more than the increase from a view.
_etmc.push(['setUserInfo', {
'email': 'INSERT_EMAIL_OR_UNIQUE_ID',
'details': {
'gender': 'female',
'otherCustomAttribute', 'myValue'
}
}]);
Track Page Views: trackPageView
The first element you pass in basically represents a method name which takes multiple variables. trackPageView accepts the following parameters alone or combined:
To track the view of a content or product, use this code:
| Key | Value |
|---|---|
'item' |
Product code (String) |
'search' |
Search term (String) |
'category' |
Catgeory (String) |
The most simple versions use one of the following lines
// product page viewed
_etmc.push(['trackPageView', { 'item': 'INSERT_PRODUCT_CODE' }]);
// category viewed
_etmc.push(['trackPageView', { 'category': 'INSERT_CATEGORY' }]);
// search executed
_etmc.push(['trackPageView', { 'search': 'INSERT_SEARCH_TERM' }]);
But of course these can also be combined: If the user came to the page using your search you can optionally use the following extended snippet:
_etmc.push(['trackPageView', { 'item': 'INSERT_PRODUCT_CODE','search': 'INSERT_SEARCH_TERM' }]);
Track Items in Cart: trackCart
This should be run each time a product is added or removed from the cart, the quantity is changed or when the purchase is finalized.
_etmc.push(['trackCart', {
'cart': [
{
'item': 'INSERT_ITEM',
'quantity': 'INSERT_QUANTITY',
'price': 'INSERT_PRICE',
'unique_id': 'INSERT_UNIQUE_ID'
},
{
'item': 'INSERT_ITEM',
'quantity': 'INSERT_QUANTITY' ,
'price': 'INSERT_PRICE' ,
'unique_id': 'INSERT_UNIQUE_ID'
}
]
}]);
Definitions:
| Key | Definition |
|---|---|
item |
Matches the field mapped to ProductCode in the catalog. |
quantity |
The number of items added for the particular SKU. |
price |
The price at the time of adding an item to the cart. |
unique_id |
Matches the field mapped to the SKUId in the catalog. When these items match it ensures the exact record in the catalog, including all specific attributes like color and size, is tied to the cart rather than just the ProductCode |
Important: Always include the entire cart in this call because it will overwrite whatever was stored before. Therefore, in order to remove one row, simply pass in all other rows that were not deleted.
The official docs state that one should use the following to remove all cart line items at once:
_etmc.push(['trackCart', { 'clear_cart': true } ]);
Track Purchases / Conversions: trackConversion
_etmc.push(['trackConversion', {
'cart': [
{
'item': 'INSERT_ITEM',
'quantity': 'INSERT_QUANTITY',
'price': 'INSERT_PRICE',
'unique_id': 'INSERT_UNIQUE_ID'
},
{
'item': 'INSERT_ITEM',
'quantity': 'INSERT_QUANTITY',
'price': 'INSERT_PRICE',
'unique_id': 'INSERT_UNIQUE_ID'
}
],
// OPTIONAL PARAMETERS
'details': {
'AttributeName': 'Value'
}
// END OPTIONAL PARAMETERS
}]);
| Key | Description |
|---|---|
cart |
same as for trackCart; see above for more details |
order_number |
mentioned in official docs but not actually supported |
discount |
mentioned in official docs but not actually supported |
shipping |
mentioned in official docs but not actually supported |
details |
optional: Given the similar format this could have something to do with affinity attributes but the effect remains unclear TBC |
Tracking overhead cost
Not actually working!
The official docs state that you can in fact track order_number, discount and shipping as separate fields and that those are then used to calculate the line item cost together with their respective overhead. While looking into collect.js however, those fields seem to get ignored when calling back to the server.
What to do instead: Simply calculate final prices on an order-line-item level before sharing it with the Collect Code. Shipping Cost have no bearing on the recommendation but if you have to track it, send it in as an order-line-item.
// non-working example from official docs
_etmc.push(['trackConversion', {
'cart': [
{
'item': '123',
'quantity': '2',
'price': '10.00',
'unique_id': '123'
}
],
// OPTIONAL PARAMETERS
'order_number': '123456', // fact check: not supported by collect.js
'discount': '2.00', // fact check: not supported by collect.js
'shipping': '5.00' // fact check: not supported by collect.js
// END OPTIONAL PARAMETERS
}]);
Track Custom Event: trackEvent
From what collect.js shows, only name and details are actually send to the server. The field details is optional!
_etmc.push(['trackEvent', {
'name': 'INSERT_CUSTOM_EVENT_NAME',
'details': {
'gender': 'female',
'otherCustomAttribute', 'myValue'
}
}]);
This is untested code and hence further information will be added later once we’ve seen it in action.
Track User Wishlist: trackWishList
Allows you to store contact wishlists for items on your website.
_etmc.push(['trackWishlist', {
'items': ['INSERT_ITEM_1', 'INSERT_ITEM_2', 'INSERT_ITEM_3'],
'skus': ['INSERT_UNIQUE_ID_1', 'INSERT_UNIQUE_ID_2', 'INSERT_UNIQUE_ID_3']
}]);
| Key | Definition |
|---|---|
item |
Matches the field mapped to ProductCode in the catalog. |
unique_id |
Matches the field mapped to the SKUId in the catalog. |
According to the offical docs, handing in skus is optional but I haven’t tested that. Make sure the 2 arrays have the same length as the first item in one list is mapped to the first in the other; the second to the second; and so on and so forth.
The contact’s wishlist data is replaced by each subsequent trackWishlist call. Therefore, make sure to always include the entire current list and remove entries from the call that the user deleted from it.