Implementing Sort'd Analytics Tracking for Headless Stores

1. Introduction

This documentation explains how to implement Sort’d Merchandiser’s client-side tracking into a headless Shopify storefront (e.g., Hydrogen, Next.js, Remix, custom React).

Tracking is essential for enabling Sort’d’s automated merchandising features, performance scoring, and product ranking. This guide covers how tracking works, the requirements needed before implementation, and the events the tracker must send.

Although this guide focuses on a Hydrogen / React implementation, the tracking script can be integrated into any online store or frontend framework capable of running JavaScript in the browser. View Required Events to implement your own custom solution.


All analytics features will be available once this tracking is implemented - except for A/B Testing.

Sort'd Merchandiser A/B Testing works by duplicating a collection and randomly sending users to one of those two collections. You may implement your own version of this, or use an A/B Testing software, to achieve the same result.

If you are ever unsure about anything during implementation, your Sort’d Merchandiser representative is available to support you throughout the entire setup process.


2. Endpoint Requirement

The tracking library sends event data to a backend endpoint that your Sort’d Merchandiser representative will provide. If you have not yet already, get in contact with your representative at info@beefy-nachos.com.

  • This endpoint must be passed into the tracker when constructing it.
  • Your Sort’d rep will provide you with an endpoint and verify that your store is set up for tracking ingestion.

3. Prerequisites

Before implementing tracking, please ensure the following are complete:

1. A Shopify Store

A valid Shopify store is required, since Sort’d Merchandiser integrates directly with Shopify’s APIs, collections, and storefront product data.

2. Sort’d Merchandiser Theme App Extension Enabled

Even if you do not use Shopify’s Online Store / Liquid themes, the Sort’d Merchandiser theme app extension must be enabled on your published Shopify theme.

When you join Sort’d Premium, the app will prompt you to enable this extension.

3. A Shopify Checkout

If your store uses a non-Shopify checkout - purchase events won't be tracked. You may implement your own custom tracking if this applies to your store. Contact your Sort’d representative if this is required.

4. Your Storefront URL Must Match Your Checkout Complete URL

Sort’d relies on consistent domain behaviour to correctly attribute product and collection performance with sales performance.

If your storefront domain differs from your checkout completion domain: Contact your Sort’d representative so we can adjust your configuration accordingly.

Note: your checkout URL (where billing, payment, etc. information is added) and checkout complete URL may be different, this is not an issue. Only your store's URL and the checkout complete URL must be consistent.


4. Supported Environments

This documentation focuses on how to implement tracking in:

  • Hydrogen (React Server Components)
  • React / Next.js / Remix
  • Any modern Single Page Application

However, the tracker is framework-agnostic and can be implemented in:

  • Vue/Nuxt
  • Svelte/SvelteKit
  • Vanilla JavaScript storefronts
  • Custom frameworks or static sites

The only requirement is that the tracker runs in the browser, not during server-side rendering.


5. Required Events

Before reviewing the required events, it is important to understand how sessions work within Sort'd tracking.

Sort'd Merchandiser tracking package will handle these events for you. You only need to review them if you'd like to understand how they work or if you're implementing a custom solution without the Sort'd Merchandiser tracking package. Otherwise skip to section 7.

Session ID Requirements

Sort’d tracking uses a session ID to associate all user activity within a single browsing session.

If you are using the Sort’d tracking framework, this is handled automatically. However, if you are implementing a custom tracking solution, you must:

  • Generate a session ID once per session
  • Store it in sessionStorage    under the key: sessionIdCm   
  • Use this same session ID for all events until the session ends

Session ID Format

The session ID must follow the format used internally by the Sort’d tracker:

<base36 timestamp><random base36 string>-<1–100 random integer>

Example structure:

lg2k9sj4q3f8rw2...-57

This must be a string, and it must remain stable for the duration of the user session.


Below are the core events the storefront must report to Sort’d. These events allow Sort’d to understand user behaviour and optimise your merchandising automatically.

The Sort’d tracking framework (described later in this documentation) automatically handles all of these events for you. Only implement these manually if you choose to build a custom tracking solution.


If you prefer not to use our tracking package, ensure:

  • You send all required events listed below
  • You send them with the correct data fields
  • You send them to the endpoint provided by your Sort’d representative
  • You do not send multiple events of the same data. Saving and checking against sessionStorage   is recommended here.

As long as these requirements are met, you are free to implement tracking in any way that suits your project.


Required Events and Payloads

Each event must include the fields defined in the Sort’d tracker’s eventData    payload:

Fields required in every event

  • event_type    — the event name (string)
  • timestamp    — ISO timestamp of when the event occurred (string)

    session_id    — the session ID stored in sessionStorage.sessionIdCm    (string)

    store    — your Shopify store domain (string)

    page_data    — an object containing:

    • all event‑specific fields listed below

1. Collection View

Triggered when a user views a collection page.

Event Type: collection_view   

Required page_data    fields:

  • collection_id    (number)
  • multiplier    (number — must be 1   )**

Triggered when a user views a collection page.

Event Type: collection_view   

Payload:

  • collection_id    (number) — Shopify numeric collection ID

2. Product View

Triggered when a user views a product page.

Event Type: product_view   

Required page_data    fields:

  • product_id    (number)
  • referrer    (number — optional; defaults to 0)
  • multiplier    (number — must be 1   )**

Triggered when a user views a product page.

Event Type: product_view   

Payload:

  • product_id    (number) — Shopify numeric product ID
  • referrer    (number) — optional; the collection ID the user came from. If the user did not come from a collection, set this to 0  

3. Product Add to Cart

Triggered when a user adds a product to their cart.

Event Type: product_add   

Required page_data    fields:

  • product_id    (number)
  • multiplier    (number — must be 1   )**

Triggered when a user adds a product to their cart.

Event Type: product_add   

Payload:

  • product_id    (number)

4. Product Card (Grid) View

Triggered when product cards appear in the user’s viewport (e.g., on collection grids).

Event Type: product_card_view   

Required page_data    fields:

  • ids    (number[]) — list of unique product IDs
  • collection_id    (number)
  • multiplier    (number — must be 1   )**

Triggered when product cards appear in the user’s viewport (e.g., on collection grids).

Event Type: product_card_view   

Payload:

  • ids    (number[]) — list of product IDs visible
  • collection_id    (number)

6. Background Summary

You should now understand:

  • The purpose of this documentation
  • The requirements needed before implementing tracking
  • That you may use Sort’d’s tracking framework or build your own, as long as the required events are sent
  • The event types and data fields your storefront must send

The next section will explain how to install and initialise the official Sort’d tracking package in a Hydrogen/React project.


7. Installing and Initialising the Sort'd Tracking Package

This section explains how to:

  1. Install the Sort'd tracking npm package
  2. Create a site‑wide tracking instance using a React provider
  3. Initialise the tracker with your store and endpoint details

If you use the provided tracking package, it will automatically handle all required events described earlier.

7.1 Install the npm package

Install the Sort'd tracking package into your headless Shopify project:

npm install @beefynachos/cm-tracker # or yarn add @beefynachos/cm-tracker

7.2 Creating a site‑wide tracker provider

To avoid creating multiple tracker instances and to make tracking easy to access throughout your app, we recommend wrapping your application in a React context provider.

In the example below, the CmTrackerProvider   :

  • Creates a single CMEventTracker    instance using useRef   
  • Initialises it in a useEffect    hook (so it only runs in the browser)
  • Exposes the tracker instance via React context so any component can access it with a useCmTracker()    hook
import {createContext, useContext, useEffect, useRef} from 'react';
import {CMEventTracker} from '@beefynachos/cm-tracker';

const TRACKING_ENDPOINT = 'https://your-endpoint-from-sortd.example.com';
const STORE_DOMAIN = 'your-store-domain.myshopify.com';

// Holds a CMEventTracker or null during SSR / before init
const CmTrackerContext = createContext(null);

export function CmTrackerProvider({children}) {
const trackerRef = useRef(null);

useEffect(() => {
// Only run in the browser
if (typeof window === 'undefined') return;

if (!trackerRef.current) {
  trackerRef.current = new CMEventTracker({
    store: STORE_DOMAIN,
    endpoint: TRACKING_ENDPOINT,
    debug: false, // set to true to log debug information in the console
  });
}
}, []);

return (
<CmTrackerContext.Provider value={trackerRef.current}>
  {children}
</CmTrackerContext.Provider>
);
}

export function useCmTracker() {
return useContext(CmTrackerContext);
}

Key points:

  • The tracker is only created once per page load.
  • It is only initialised in the browser (typeof window === 'undefined'    check avoids SSR issues).
  • All children of CmTrackerProvider    can access the tracker via useCmTracker()   .

7.3 Wrapping your application (Hydrogen / Remix example)

In a Hydrogen/Remix app, you typically wrap the root <App />    component with providers.

In the reference implementation, the App    component in root.jsx    wraps the content with both Shopify's Analytics.Provider    and CmTrackerProvider   :

export default function App() {
const data = useRouteLoaderData('root');

if (!data) {
return <Outlet />;
}

return (
<Analytics.Provider
  cart={data.cart}
  shop={data.shop}
  consent={data.consent}
>
  <CmTrackerProvider>
    <PageLayout {...data}>
      <Outlet />
    </PageLayout>
  </CmTrackerProvider>
</Analytics.Provider>
);
}

Here:

  • CmTrackerProvider    wraps the entire PageLayout    and all nested routes.
  • Any route/component rendered inside <Outlet />    can call useCmTracker()    and send tracking events (collection views, product views, add‑to‑cart, product card views). fileciteturn2file1

You should follow a similar pattern in your project:

  1. Import CmTrackerProvider    in your root layout / app entry file.
  2. Wrap your top‑level layout or router with <CmTrackerProvider>   .
  3. Use the useCmTracker()    hook anywhere you need to send events.

7.4 Initialising the tracker: required data

When creating a new CMEventTracker    instance, the following options are required:

new CMEventTracker({
store: string; // required – your Shopify store domain
endpoint: string; // required – Sort'd tracking endpoint
debug?: boolean; // optional – logs internal behaviour to the console when true
});

store   

  • Must be your Shopify store domain, e.g. mystore.myshopify.com    or your primary .com    domain if advised by your Sort'd representative.
  • This value is sent with every event and is used to associate tracking data with the correct store.

endpoint   

  • Must be the tracking endpoint provided by your Sort'd Merchandiser representative.
  • All events (collection views, product views, add‑to‑cart, card views) will be sent to this endpoint.

debug    (optional)

  • When true   , the tracker logs useful debug information to the browser console (initialisation, events being sent, warnings when required data is missing).
  • For production environments, we recommend setting this to false   .

7.5 Ensuring your endpoint is permitted

For Hydrogen applications, tracking endpoints may need to be permitted in your Content Security Policy. If not, you may experience CORS errors.

The following is how this can be implemented, using the Hydrogen's starx as an example.


entry.server.jsx  

  const {nonce, header, NonceProvider} = createContentSecurityPolicy({
    shop: {
      checkoutDomain: context.env.PUBLIC_CHECKOUT_DOMAIN,
      storeDomain: context.env.PUBLIC_STORE_DOMAIN,
    },
    connectSrc: [
      'https://your-example-endpoint.com', // Add your custom endpoints here
    ]
  });

vite.config.jsx  

export default defineConfig({
  plugins: [hydrogen(), oxygen(), reactRouter(), tsconfigPaths()],
  build: {
    // Allow a strict Content-Security-Policy
    // without inlining assets as base64:
    assetsInlineLimit: 0,
  },
  ssr: {
    optimizeDeps: {
      /**
       * Include dependencies here if they throw CJS<>ESM errors.
       * For example, for the following error:
       *
       * > ReferenceError: module is not defined
       * >   at /Users/.../node_modules/example-dep/index.js:1:1
       *
       * Include 'example-dep' in the array below.
       * @see https://vitejs.dev/config/dep-optimization-options
       */
      include: ['set-cookie-parser', 'cookie', 'react-router'],
    },
  },
  server: {
    allowedHosts: ['.tryhydrogen.dev', 'your-example-endpoint.com'],
  },
});

8. Implementing Tracking Events

This section explains how to fire each required tracking event from your Hydrogen/React storefront using the cmTracker    instance provided by CmTrackerProvider   .

All examples below assume:

  • Your app is wrapped in <CmTrackerProvider>    at the root level
  • You access the tracker instance via the useCmTracker()    hook

If you are building a custom tracking solution, you can follow the same patterns but call your own tracking functions instead, as long as you send the correct event payloads defined in Required Events.

The following examples build on Hydrogen's starter, your codebase may be different and application of this tracking should be updated accordingly.


8.1 Collection View

What it is

Tracks when a user views a collection page.

When it fires

On initial render of a collection page, once the collection data is available on the client.

Data required

cmTracker.trackCollectionView    must receive an object with:

  • collectionId    — number (Shopify numeric collection ID)

The example below shows the implementation from collections.$handle.jsx   . The Shopify GraphQL API returns a collection ID as a global ID string (gid://shopify/Collection/...   ), so we convert it into a numeric ID before sending it to the tracker.

import {useEffect} from 'react';
import {useLoaderData} from 'react-router';
import {useCmTracker} from '~/components/CmTracker';

export default function Collection() {
const {collection} = useLoaderData();
const cmTracker = useCmTracker();

useEffect(() => {
if (!cmTracker || !collection?.id) return;

try {
  const id = parseInt(
    collection.id.replace('gid://shopify/Collection/', ''),
    10,
  );

  cmTracker.trackCollectionView({
    collectionId: id,
  });
} catch (error) {
  console.error(error);
}
}, [cmTracker, collection?.id]);

// ...render collection UI
}

8.2 Product Card View (Collection Grid)

What it is

Tracks when individual product cards (tiles) become visible in the viewport on a collection page.

When it fires

Whenever a product card scrolls into view for the first time on a collection page.

Data required

Product card view events are batched by the tracker. You must pass the Shopify product ID (GID string)

  • productId    — string (Shopify product GID, e.g. gid://shopify/Product/1234567890   )
  • collection_id    — numeric collection ID

Why react-intersection-observer   ?

We recommend using the react-intersection-observer    package because it:

  • Provides a simple React component (<InView>   ) that wraps IntersectionObserver
  • Avoids manual observer setup/cleanup
  • Makes it easy to trigger logic once per card when it becomes visible

Install it:

npm install react-intersection-observer
# or
yarn add react-intersection-observer

Implementation example (ProductItem.jsx   )

import {InView} from 'react-intersection-observer';
import {useEffect, useState} from 'react';
import {useCmTracker} from '~/components/CmTracker';

export function ProductItem({product, loading, collectionId = null}) {
const cmTracker = useCmTracker();
const hasTrackedViewRef = useRef(false);
const productId = product.id; // Shopify product GID

// When the product card has been in view, notify the tracker once
useEffect(() => {
if (!hasTrackedViewRef.current  || !cmTracker) return;

cmTracker.setProductCardUrlsToSend = productId;
}, [hasBeenInView, cmTracker, productId]);

return (
<InView
  as="div"
  triggerOnce
  onChange={(inView) => {
    if (inView) {
      hasTrackedViewRef.current = true;
    }
  }}
>
  {/* Product link/content here */}
</InView>
);
}

Implementation steps:

  1. Install react-intersection-observer   .
  2. Wrap each product card (on collection pages) in an <InView>    component.
  3. Track a hasTrackedViewRef    ref so each card is only tracked once.
  4. When hasTrackedViewRef    becomes true   , call cmTracker.setProductCardUrlsToSend = product.id   .

The tracker will batch multiple product IDs and send them together after a short delay.

You must check if product card is also used on other pages, if so, ensure cmTracker.setProductCardUrlsToSend   is not used. Only events on collection pages should be used.


8.3 Product View (with Referrer)

What it is

Tracks when a user clicks a product card on a collection page, including which collection the click came from.

When it fires

When the user clicks a product card link on a collection page, just before navigation to the product page.

Data required

cmTracker.trackProductView    must receive:

  • productId    — number (Shopify numeric product ID)
  • referrer    — number (Shopify numeric collection ID the user came from)

Implementation example (ProductItem.jsx   )

const onProductClick = () => {
  if (!cmTracker) return;

  try {
    const referrer = collectionId
    parseInt(
      (collectionId || '').replace('gid://shopify/Collection/', ''),
      10,
    ) || null;

    if (!referrer) {
      return;
    }

    cmTracker.trackProductView({
      productId: parseInt(
        product.id.replace('gid://shopify/Product/', ''),
        10,
      ),
      referrer,
    });
  } catch (error) {
    console.error(error);
  }
};

return (
<InView /* ... */>
<Link
  className="product-item"
  key={product.id}
  onClick={onProductClick}
  prefetch="intent"
  to={variantUrl}
>
  {/* product image, title, price */}
</Link>
);

Notes:

  • collectionId    and product.id    are GraphQL global IDs; both are converted to numeric IDs before being sent.
  • If collectionId    is not available or cannot be parsed, the referrer event is skipped.
  • This event is in addition to the product view event fired on the product page itself (see below), but the tracker de‑duplicates product views per session.

8.4 Product View (Product Page)

What it is

Tracks when a user views the product detail page (PDP).

When it fires

On initial render of the product page, once product data is available on the client.

Data required

cmTracker.trackProductView    must receive:

  • productId    — number (Shopify numeric product ID)

The example below is from products.$handle.jsx   :

import {useEffect} from 'react';
import {useLoaderData} from 'react-router';
import {useCmTracker} from '~/components/CmTracker';

export default function Product() {
const {product} = useLoaderData();
const cmTracker = useCmTracker();

useEffect(() => {
if (!cmTracker || !product?.id) return;

try {
  const id = parseInt(
    product.id.replace('gid://shopify/Product/', ''),
    10,
  );

  cmTracker.trackProductView({
    productId: id,
  });
} catch (error) {
  console.error(error);
}
}, [cmTracker, product?.id]);

// ...render product UI
}

The tracker will automatically:

  • De‑duplicate product view events per session (so the same product is not counted multiple times).
  • Send referrer = 0    when no explicit referrer is passed.

8.5 Product Add to Cart

What it is

Tracks when a user adds a product to their cart from the product page.

When it fires

When the user clicks the Add to cart button and a variant is available for sale.

Data required

cmTracker.trackProductAddToCart    must receive:

  • productId    — number (Shopify numeric product ID)

Implementation from ProductForm.jsx   , specifically on the AddToCartButton   :

import {useCmTracker} from '~/components/CmTracker';
import {AddToCartButton} from './AddToCartButton';

export function ProductForm({productOptions, selectedVariant, productId}) {
const cmTracker = useCmTracker();

return (
<div className="product-form">
  {/* product option pickers */}

  <AddToCartButton
    disabled={!selectedVariant || !selectedVariant.availableForSale}
    onClick={() => {
      open('cart');

      if (cmTracker && selectedVariant) {
        try {
          cmTracker.trackProductAddToCart({
            productId: parseInt(
              productId.replace('gid://shopify/Product/', ''),
              10,
            ),
          });
        } catch (error) {
          console.error(error);
        }
      }
    }}
    lines={
      selectedVariant
        ? [
            {
              merchandiseId: selectedVariant.id,
              quantity: 1,
              selectedVariant,
            },
          ]
        : []
    }
  >
    {selectedVariant?.availableForSale ? 'Add to cart' : 'Sold out'}
  </AddToCartButton>
</div>
);
}

This ensures that each successful add‑to‑cart interaction is recorded with the correct product ID.

With these implementations in place, your storefront will send all required events to Sort'd Merchandiser, enabling accurate performance tracking and automated merchandising.


9. Testing Your Tracking Implementation

To ensure your tracking events are firing correctly, you can test them directly in your browser using Chrome DevTools.

How to test events in Chrome

  1. Open Chrome DevTools → Right‑click → InspectNetwork tab.
  2. Enable Preserve log so requests are not cleared on navigation.

    In the Filter box, type part of your Sort’d tracking endpoint URL (e.g. collect  , cm  , or your provided endpoint path).

    Perform actions on your site:

    • Navigate to a collection page → expect a collection_view   event.
    • Scroll until product cards enter view → expect a product_card_view   event.
    • Click a product from a collection → expect a product_view   event with a referrer.
    • Load the product page directly → expect a product_view   event without a referrer.
    • Add a product to cart → expect a product_add   event.

      Click each request to view:

    • The payload containing event data
    • The HTTP status (should be 200–299)

Remember, when testing the cmEventTracker  will exclude products/collections that have already been tracked.

Optional: Enable debug mode

If you initialise the tracker with debug: true  , it logs helpful information:

new CMEventTracker({
   store: STORE_DOMAIN,
   endpoint: TRACKING_ENDPOINT,
   debug: true,
});

You’ll see messages in the Console such as:

  • "Session started"
  • "Sending product_view event"
  • "Queued product card IDs: [...]"

Sort’d validation

Once you have implemented tracking, your Sort’d Merchandiser representative can run a full validation on your live environment.

They will check:

  • All events are firing
  • All required fields are sent
  • Events are correctly attributed to store sessions

10. Product Purchase Events (Checkout Tracking)

Sort’d Merchandiser tracks completed purchases using a Shopify Pixel. This:

  • Requires no custom development on your headless storefront
  • Is implemented and maintained by the Sort’d team
  • Automatically associates purchases with sessions and prior events

URL consistency requirement

For purchase attribution to work correctly:

  • Your storefront URL must match your checkout complete URL.

This means:

  • https://yourstore.com   → storefront
  • https://yourstore.com/checkouts/.../thank_you   → checkout complete page

If your store uses different domains (e.g., storefront on www.   but checkout complete on bare domain), contact your Sort’d representative. We can configure multi-domain handling if applicable.

Test purchases

To confirm that purchase events are flowing correctly, Sort’d may need to perform a test purchase.

If you would like Sort’d to complete a test purchase on your behalf:

  • Provide instructions for the test order
  • Provide any required discount codes, bypass instructions, or test products

Afterward, Sort’d will verify that:

  • The purchase pixel is firing correctly
  • Purchases are linked to sessions and event histories
  • Revenue attribution matches what Shopify reports

11. Summary and Support

This documentation has covered:

  • The purpose and role of Sort’d’s tracking framework
  • All requirements and prerequisites before implementation
  • The full list of required tracking events and their payloads
  • How to install and initialise the Sort’d tracking package
  • How to implement each event in a Hydrogen/React environment
  • How to test your tracking implementation
  • How purchase events are tracked through the Shopify Pixel

If you have any questions or would like help during implementation, the Sort’d team is here to assist.

Support Contact

Reach out to your Sort’d Merchandiser representative directly or contact us at:

📨 info@beefy-nachos.com

We’re always happy to help ensure your tracking is implemented correctly and your merchandising insights are accurate.

Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.