Installing on Hydrogen Site with Shopify

This guide will walk you through how to install Aimtell on a Shopify Hydrogen headless site.

A few prerequisites and notes:
  1. This is a general guide. The provided code may need to be adapted to be compatible with your specific Hydrogen version and configuration.
  2. 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.
  3. Service worker must be uploaded and the standard Aimtell tracking code in place in Hydrogen.  (See Aimtell documentation for installation options)
  4. Tracking third party analytics requires configuring consent (refer to the Shopify documentation for details)

  5. 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 the AimtellJsTags.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:


  1. If using hydrogen version 2024.4.3 or newer skip to the next step, otherwise use the Shopify documentation to configure analytics tracking.
  2. Follow the steps to include the custom analytics component detailed below.
    1. Create a component to subscribe to the Shopify analytics events and fire the appropriate Aimtell events (AimtellJsTags.jsx in the example below).
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.