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
sessionStorageunder 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
sessionStorageis 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 insessionStorage.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 be1)**
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 be1)**
Triggered when a user views a product page.
Event Type: product_view
Payload:
product_id(number) — Shopify numeric product IDreferrer(number) — optional; the collection ID the user came from. If the user did not come from a collection, set this to0
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 be1)**
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 IDscollection_id(number)multiplier(number — must be1)**
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 visiblecollection_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:
- Install the Sort'd tracking npm package
- Create a site‑wide tracking instance using a React provider
- 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
CMEventTrackerinstance usinguseRef - Initialises it in a
useEffecthook (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
CmTrackerProvidercan access the tracker viauseCmTracker().
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:
CmTrackerProviderwraps the entirePageLayoutand all nested routes.- Any route/component rendered inside
<Outlet />can calluseCmTracker()and send tracking events (collection views, product views, add‑to‑cart, product card views). fileciteturn2file1
You should follow a similar pattern in your project:
- Import
CmTrackerProviderin your root layout / app entry file. - Wrap your top‑level layout or router with
<CmTrackerProvider>. - 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.comor your primary.comdomain 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:
- Install
react-intersection-observer. - Wrap each product card (on collection pages) in an
<InView>component. - Track a
hasTrackedViewRefref so each card is only tracked once. - When
hasTrackedViewRefbecomestrue, callcmTracker.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:
collectionIdandproduct.idare GraphQL global IDs; both are converted to numeric IDs before being sent.- If
collectionIdis 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 = 0when 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
- Open Chrome DevTools → Right‑click → Inspect → Network tab.
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_viewevent. - Scroll until product cards enter view → expect a
product_card_viewevent. - Click a product from a collection → expect a
product_viewevent with a referrer. - Load the product page directly → expect a
product_viewevent without a referrer. Add a product to cart → expect a
product_addevent.Click each request to view:
- The payload containing event data
- The HTTP status (should be 200–299)
- Navigate to a collection page → expect a
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→ storefronthttps://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.