This guide will walk you through how to install Aimtell on a Shopify Hydrogen headless site.
- This is a general guide. The provided code may need to be adapted to be compatible with your specific Hydrogen version and configuration.
- The Aimtell Shopify plugin must be installed via the Shopify admin. This is necessary to ensure the Shopify website is set up properly in Aimtell and the Shopify event webhooks are added. Complete this step in addition to step 3 below.
- Service worker must be uploaded and the standard Aimtell tracking code in place in Hydrogen. (See Aimtell documentation for installation options)
-
Tracking third party analytics requires configuring consent (refer to the Shopify documentation for details)
- The abandoned browse setting in the Shopify settings in the Aimtell dashboard will not enable/disable tracking or change the delay in Hydrogen; the settings for Hydrogen are defined within the
_aimtellShopifyWatchBrowse()
function (as shown in theAimtellJsTags.jsx
component below) and should match the desired settings in the dashboard:
// Shopify abandoned browse settings
//enable or disable browse abandonment tracking
var _aimtellShopifyAbandonedBrowse = true;
//flag to prevent multiple tracking events, defaults to false
var _aimtellShopifyBrowseAbandoned = false;
//convert minutes to milliseconds for setTimeout, default 10 minutes
var _aimtellShopifyAbandonedBrowseDelay = 10 * 60000;
var _aimtellTimeout;
To set up Aimtell Shopify analytics tracking in Hydrogen:
- If using
hydrogen
version2024.4.3
or newer skip to the next step, otherwise use the Shopify documentation to configure analytics tracking. - Follow the steps to include the custom analytics component detailed below.
- Create a component to subscribe to the Shopify analytics events and fire the appropriate Aimtell events (
AimtellJsTags.jsx
in the example below).
- Create a component to subscribe to the Shopify analytics events and fire the appropriate Aimtell events (
import {useAnalytics} from '@shopify/hydrogen';
import {useEffect} from 'react';
export function AimtellJsTags({checkoutDomain}) {
const {subscribe, register} = useAnalytics();
// Register this analytics integration - this will prevent any analytics events
// from being sent until this integration is ready
const {ready} = register('Aimtell');
useEffect(() => {
// Check cart for items
function _aimtellShopifyCartChecker(cartData){
const cart = cartData;
// If there's no cart data and we can't get the cart token, return false
if(!cart){
return false;
}
const cartToken = cart.id.split('/').pop();
const products = cart.lines.edges;
// If there are items in cart, loop through each item
if(cart.totalQuantity > 0){
// Record cart information for add to cart features
if(!_aimtellGetCookie('_aimtellCartToken-' + cartToken)){
var _aimtellShopifyData = {};
_aimtellShopifyData.subscriber = _aimtellSubscriberID;
_aimtellShopifyData.cart = cartToken;
_aimtellShopifyData.idSite = _at.idSite;
_aimtellShopifyData.owner_uid = _at.owner;
_aimtellShopifyData.item_count = cart.totalQuantity;
// Grab the first item and use the variables
try {
var selected_options_query_string = '';
var first_product = products[0].node.merchandise;
if(first_product.selectedOptions.length > 0){
for (var i = 0; i < first_product.selectedOptions.length; i++) {
(i > 0) ? selected_options_query_string += '&' : selected_options_query_string += '?';
selected_options_query_string += first_product.selectedOptions[i].name + '=' + first_product.selectedOptions[i].value;
}
}
_aimtellShopifyData.variables = {};
_aimtellShopifyData.variables.price = (first_product.price.amount);
_aimtellShopifyData.variables.url = window.location.host + '/products/' + first_product.product.handle + selected_options_query_string;
_aimtellShopifyData.variables.icon = first_product.image.url;
_aimtellShopifyData.variables.title = first_product.product.title + ' - ' + first_product.title;
} catch (err) {
console.warn('[aimtell]' + err);
}
var postData = JSON.stringify(_aimtellShopifyData);
var postURL = _aimtellAPI+'/shopify/cookie';
// Fire off call to record data
var xmlhttp = new XMLHttpRequest();
xmlhttp.open('POST', postURL,true);
xmlhttp.setRequestHeader('Content-Type', 'text/plain;charset=UTF-8');
xmlhttp.send(postData);
// Mark token as recorded
_aimtellSetCookie('_aimtellCartToken-' + cartToken, true, 1)
}
}
// Track each item in the cart as an event
for (var i = 0; i < products.length; i++) {
// Get the product id
var product_id = products[i].node.merchandise.id.split('/').pop();
// Only track each item once
if(product_id != undefined && !_aimtellGetCookie('_aimtellAddToCart-' + product_id)){
_aimtellTrackEvent('Item', 'Add To Cart', products[i].node.merchandise.product.title)
_aimtellSetCookie('_aimtellAddToCart-' + product_id, true, 1)
}
}
}
// Watch for browse abandonment
function _aimtellShopifyWatchBrowse(){
// Shopify abandoned browse settings
var _aimtellShopifyAbandonedBrowse = true; // Enable or disable browse abandonment tracking
var _aimtellShopifyBrowseAbandoned = false; // Flag to prevent multiple tracking events, defaults to false
var _aimtellShopifyAbandonedBrowseDelay = 10 * 60000; // Convert minutes to milliseconds for setTimeout, default 10 minutes
var _aimtellTimeout;
async function _aimtellGetTrackProduct() {
console.log('[aimtell] idle timer expired, tracking');
await _aimtellGetShopifyProductDetails(checkoutDomain).then(product_data =>{
if(product_data && typeof product_data == 'object'){
if (product_data.available && typeof product_data.details == 'object') {
_aimtellTrackEvent('Aimtell', 'Shopify-Page-Inactive', 'https://' + checkoutDomain + window.location.pathname, null, product_data.details);
// Remove event listeners for and set flag
_aimtellShopifyBrowseAbandoned = true;
const eventListeners = ['load', 'mousemove', 'keydown'];
eventListeners.forEach(function(event) {
window.removeEventListener(event, _aimtellResetTimer);
});
console.log('[aimtell] tracked browse abandon event');
}
else{
console.log('[aimtell] product not available');
console.log('[aimtell] browse abandon event not tracked');
}
}
else{
console.log('[aimtell] no product data available');
console.log('[aimtell] browse abandon event not tracked');
}
}).catch(error =>{
console.error('[aimtell] error fetching product details: ' + error);
console.log('[aimtell] browse abandon event not tracked');
});
}
async function _aimtellGetShopifyProductDetails() {
// Get product information and availability
var product_data = {};
product_data.available = false;
product_data.details = {};
try {
const product_availability_check = await fetch('https://' + checkoutDomain + window.location.pathname + '.js');
if (product_availability_check.ok) {
var product_availability = await product_availability_check.json();
product_data.available = product_availability.available;
if (product_data.available) {
const product_details_check = await fetch('https://' + checkoutDomain + window.location.pathname + '.json');
if(product_details_check.ok){
var product_details = await product_details_check.json();
if (product_details.product.image && product_details.product.image.src) product_data.details.icon = product_details.product.image.src;
product_data.details.price = product_details.product.variants && product_details.product.variants[0] && product_details.product.variants[0].price;
product_data.details.url = window.location.origin + window.location.pathname;
product_data.details.title = product_details.product.title;
}
}
}
} catch (error) {
console.error('[aimtell] error fetching product details: ' + error);
}
return product_data;
}
function _aimtellResetTimer() {
// Only run on product pages
if (window.location.href.indexOf('products') > 0) {
clearTimeout(_aimtellTimeout);
_aimtellTimeout = setTimeout(_aimtellGetTrackProduct, _aimtellShopifyAbandonedBrowseDelay);
}
}
if(_aimtellShopifyAbandonedBrowse && !_aimtellShopifyBrowseAbandoned){
console.log('[aimtell] Shopify browse abandonment enabled, with ' + _aimtellShopifyAbandonedBrowseDelay/60000 + ' minute delay');
// Add event listeners
const eventListeners = ['load', 'mousemove', 'keydown'];
eventListeners.forEach(function(event) {
window.addEventListener(event, _aimtellResetTimer);
});
}
}
// Make sure aimtell is loaded before tracking
function _aimtellTrackShopifyAnalytics(data, event, counter = 0){
counter++;
if(counter > 20){
// If Aimtell doesn't load after 20 attempts, return false
return false;
}
// Check if the necessary Aimtell dependencies are loaded
if (typeof _aimtellGetCookie === 'undefined' || typeof _aimtellTrackData == 'undefined') {
return setTimeout(() => _aimtellTrackShopifyAnalytics(data, event, counter), 250);
}
// Check the cart unless it's a product view since product pages also trigger the page viewed event
if(event !== 'product_viewed'){
_aimtellShopifyCartChecker(data.cart);
}
// Watch for browse abandonment on product pages
else {
_aimtellShopifyWatchBrowse();
}
return true;
}
// Subscribe to Shopify analytics events
subscribe('page_viewed', (data) => {
_aimtellTrackShopifyAnalytics(data, 'page_viewed');
});
subscribe('product_viewed', (data) => {
_aimtellTrackShopifyAnalytics(data, 'product_viewed');
});
subscribe('cart_viewed', (data) => {
_aimtellTrackShopifyAnalytics(data, 'cart_viewed');
});
subscribe('cart_updated', (data) => {
_aimtellTrackShopifyAnalytics(data, 'cart_updated');
});
// Mark the integration as ready
ready();
}, []);
return null;
}
b. Import the component into the
root.jsx
file.import {AimtellJsTags} from './components/AimtellJsTags';
c. Confirm that consent has been configured per the Shopify documentation (If this is not set up, the analytics events won’t fire).
d. Add the PUBLIC_CHECKOUT_DOMAIN
environment variable to the loader
data object in root.jsx
(checkoutDomain
in the example below). Note that this is the URL of your Shopify site's checkout, not the Hydrogen store URL. (e.g. https://yourshopifystore.myshopify.com vs. htttps://yourshopifystore.com)
checkoutDomain: env.PUBLIC_CHECKOUT_DOMAIN,
It will be passed into the analytics component to grab product availability and details in the abandoned browse script. (See Shopify documentation)
const product_availability_check = await fetch
('https://' + checkoutDomain + window.location.pathname + '.js');
e. Add the AimtellJsTags
component as a child of the Analytics.Provider
component (after the PageLayout
component).
<Analytics.Provider
cart={data.cart}
shop={data.shop}
consent={data.consent}
>
<PageLayout {...data}>{children}</PageLayout>
<AimtellJsTags checkoutDomain={data.checkoutDomain} />
</Analytics.Provider>
That's it, you should be good to go! Contact support@aimtell.com if you have any questions.