@getcontextual/shopify-sdk
v1.1.4
Published
Easy integration for tracking on headless Shopify sites
Readme
@getcontextual/shopify-sdk
A comprehensive tracking SDK for headless Shopify stores. Works with vanilla JavaScript, React, and any SSR framework. Features SSR-safe tracking, and automatic GTM integration.
Features
- 🚀 Framework Agnostic: Works with vanilla JS, React, and SSR frameworks like Next.js
- 🔒 SSR-Safe: All tracking methods gracefully handle server-side rendering
- 📦 Automatic GTM: Zero-configuration Google Tag Manager integration
- 📊 Comprehensive Events: Product views, collections, cart, checkout, search, and more
- 🎨 TypeScript: Full TypeScript support with Shopify type definitions
- ⚡ Auto-Flush: Automatic event batching for optimal performance
- 🔄 React Safe: Handles React Strict Mode and SSR correctly
Installation
npm install @getcontextual/shopify-sdk
# or
pnpm add @getcontextual/shopify-sdk
# or
yarn add @getcontextual/shopify-sdkPeer Dependencies: React (≥16.8.0) is optional and only needed if using React components.
Quick Start
Vanilla JavaScript
import { init, trackProductView } from "@getcontextual/shopify-sdk/vanilla";
// Initialize once
init({
merchantId: "your-store.myshopify.com",
autoTrackPageView: true,
gtmContainerId: "GTM-XXXXXXX", // Optional
});
// Track events
trackProductView(productVariant);React
import { TrackerProvider, useTracker } from "@getcontextual/shopify-sdk/react";
function App() {
return (
<TrackerProvider config={{ merchantId: "your-store.myshopify.com" }}>
<ProductPage product={product} />
</TrackerProvider>
);
}
function ProductPage({ product }) {
const { trackProductView } = useTracker();
useEffect(() => {
trackProductView(product.variant);
}, [product.variant, trackProductView]);
return <div>{product.title}</div>;
}Vanilla JavaScript API
Perfect for static sites, Vue.js, Svelte, or any framework without React.
Initialization
import { init } from "@getcontextual/shopify-sdk/vanilla";
init({
merchantId: "your-store.myshopify.com", // Required
autoTrackPageView: true, // Automatically track page views
gtmContainerId: "GTM-XXXXXXX", // Optional: GTM container ID
autoFlush: true, // Auto-flush events (default: true)
flushInterval: 100, // Flush interval in ms (default: 100)
});Tracking Events
import {
trackPageView,
trackProductView,
trackCollectionView,
trackAddToCart,
trackCheckoutStarted,
trackCheckoutCompleted,
trackSearch,
identify,
} from "@getcontextual/shopify-sdk/vanilla";
// Track page view
trackPageView();
// Track product view
trackProductView(productVariant);
// Track collection view
trackCollectionView(collection);
// Track add to cart
trackAddToCart(cartLine);
// Track checkout
trackCheckoutStarted(checkout);
trackCheckoutCompleted(checkout);
// Track search
trackSearch(query, searchResults);
// Identify user
identify({
email: "[email protected]",
phone: "+1234567890",
firstName: "John",
lastName: "Doe",
});React API
Uses React Context for dependency injection, making it easy to access tracking functions throughout your app.
Setup
import { TrackerProvider } from "@getcontextual/shopify-sdk/react";
function App() {
return (
<TrackerProvider
config={{
merchantId: "your-store.myshopify.com",
gtmContainerId: "GTM-XXXXXXX", // Optional
autoFlush: true,
flushInterval: 100,
}}
>
<YourApp />
</TrackerProvider>
);
}Using Hooks
import { useTracker } from "@getcontextual/shopify-sdk/react";
function ProductPage({ product }) {
const { trackProductView } = useTracker();
useEffect(() => {
trackProductView(product.variant);
}, [product.variant, trackProductView]);
return <div>{product.title}</div>;
}
function AddToCartButton({ cartLine }) {
const { trackAddToCart } = useTracker();
const handleClick = () => {
// Your add to cart logic
trackAddToCart(cartLine);
};
return <button onClick={handleClick}>Add to Cart</button>;
}Using Components
For automatic tracking when components mount:
import {
ProductTracking,
CollectionTracking,
AddToCartTracking,
CheckoutStartedTracking,
CheckoutCompletedTracking,
IdentifyTracking,
PageViewTracker,
} from "@getcontextual/shopify-sdk/react";
function ProductPage({ product }) {
return (
<>
<ProductTracking productVariant={product.variant} />
<h1>{product.title}</h1>
</>
);
}
function CheckoutPage({ checkout }) {
return (
<>
<CheckoutStartedTracking checkout={checkout} />
{/* Your checkout UI */}
</>
);
}Nextjs/Server-Side Rendering (SSR) Guide
All tracking methods are SSR-safe and will gracefully no-op on the server-side:
- ✅ Safe to call tracking methods during SSR
- ✅ No errors or warnings on the server
- ✅ Events only tracked on the client-side after hydration
- ✅ Works with Next.js, Remix, and other SSR frameworks
Initialize Tracker
Create a client component to initialize the tracker:
"use client";
import { useEffect, useContext, createContext } from "react";
import {
init,
trackPageView,
trackProductView,
trackCollectionView,
trackAddToCart,
trackCheckoutStarted,
trackCheckoutCompleted,
trackSearch,
identify,
} from "@getcontextual/shopify-sdk/vanilla";
import type { HeadlessShopifyConfig } from "../tracker";
interface TrackerContextValue {
trackPageView: typeof trackPageView;
trackProductView: typeof trackProductView;
trackCollectionView: typeof trackCollectionView;
trackAddToCart: typeof trackAddToCart;
trackCheckoutStarted: typeof trackCheckoutStarted;
trackCheckoutCompleted: typeof trackCheckoutCompleted;
trackSearch: typeof trackSearch;
identify: typeof identify;
}
const TrackerContext = createContext<TrackerContextValue | null>(null);
export function TrackerProvider({
merchantId: string,
}: {
merchantId: string;
}) {
useEffect(() => {
init({ merchantId, autoTrackPageView: false });
}, [config]);
const value: TrackerContextValue = {
trackPageView,
trackProductView,
trackCollectionView,
trackAddToCart,
trackSearch,
trackCheckoutStarted,
trackCheckoutCompleted,
identify,
};
return (
<TrackerContext.Provider value={value}>{children}</TrackerContext.Provider>
);
}
export function useTracker() {
const context = useContext(TrackerContext);
if (!context) {
throw new Error("useTracker must be used within a TrackerProvider");
}
return context;
}Automatic Page View Tracker
With Page Router
// components/page-view-tracker.tsx
"use client";
import { useEffect } from "react";
import { useRouter } from "next/router";
// contextual sdk is global singleton so can be accessed like this
import { trackPageView } from "@getcontextual/shopify-sdk/vanilla";
export function PageViewTracker() {
const router = useRouter();
useEffect(() => {
const handleRouteChange = () => {
void trackPageView();
};
router.events.on("routeChangeComplete", handleRouteChange);
return () => {
router.events.off("routeChangeComplete", handleRouteChange);
};
}, [router.events]);
return null;
}With App Router
"use client";
// contextual sdk is global singleton so can be accessed like this
import { useEffect, Suspense } from "react";
import { usePathname, useSearchParams } from "next/navigation";
import { useTracker } from "./tracker-provider";
function PageViewHandler() {
const pathname = usePathname();
const searchParams = useSearchParams();
const { trackPageView } = useTracker();
useEffect(() => {
trackPageView();
}, [pathname, searchParams, trackPageView]);
return null;
}
export function AppRouterPageViewTracker() {
return (
<Suspense fallback={null}>
<PageViewHandler />
</Suspense>
);
}// app/layout.tsx
export default function RootLayout({ children }) {
return (
<html>
<body>
<TrackerProvider config={{ merchantId: "my-store.myshopify.com" }}>
<AppRouterPageViewTracker />
{children}
</TrackerProvider>
</body>
</html>
);
}Using Components
"use client";
import { trackProductView } from "@getcontextual/shopify-sdk/vanilla";
export function ProductTracking({
productVariant,
}: {
productVariant: ShopifyProductVariant;
}) {
const hasTracked = useRef(false);
useEffect(() => {
if (!hasTracked.current && productVariant) {
hasTracked.current = true;
void trackProductView(productVariant);
}
}, [productVariant]);
return null;
}Using Hooks
// pages/products/[id].tsx
import { useEffect } from "react";
import { trackProductView } from "@getcontextual/shopify-sdk/vanilla";
export default function ProductPage({ product }) {
useEffect(() => {
if (product?.variant) {
void trackProductView(product.variant);
}
}, [product]);
return <div>{product.title}</div>;
}##W Key Points
- Always use
'use client': Any file that imports from@getcontextual/shopify-sdkmust have'use client'at the top - SSR-safe: All tracking functions are SSR-safe and will no-op on the server
- Initialize once: Call
init()only once, typically in a provider component - Track in useEffect: Always track events inside
useEffectto ensure they run on the client-side - Page views: Track page views when route changes (App Router:
usePathname/useSearchParams, Pages Router:router.events)
Configuration
HeadlessShopifyConfig
interface HeadlessShopifyConfig {
merchantId: string; // Required: Your Shopify store domain (e.g., 'store.myshopify.com')
version?: string; // Optional: SDK version (default: '1.0.0')
autoFlush?: boolean; // Optional: Auto-flush events (default: true)
flushInterval?: number; // Optional: Flush interval in milliseconds (default: 100)
gtmContainerId?: string; // Optional: Google Tag Manager container ID
}VanillaShopifyConfig
Extends HeadlessShopifyConfig with:
interface VanillaShopifyConfig extends HeadlessShopifyConfig {
autoTrackPageView: boolean; // Automatically track page views on init
}TypeScript Support
The package is fully typed. All Shopify types are based on @shopify/web-pixels-extension for consistency.
import type {
ShopifyProductVariant,
ShopifyCartLine,
ShopifyCheckout,
ShopifyCollection,
ShopifySearchResult,
HeadlessShopifyConfig,
} from "@getcontextual/shopify-sdk";Google Tag Manager Integration
Setup
init({
merchantId: "your-store.myshopify.com",
gtmContainerId: "GTM-XXXXXXX", // Your GTM container ID
});How It Works
- Automatic Loading: GTM script loads automatically on tracker initialization
- Event Pushing: All events are pushed to
window.dataLayer - Event Format: Events follow standard GTM data layer format
Event Structure
Events are pushed to dataLayer as:
{
event: 'product_viewed',
event_data: {
merchant_id: 'your-store.myshopify.com',
event_id: 'unique-event-id',
// ... product data, user data, etc.
}
}Note: If you don't provide a gtmContainerId, the SDK works perfectly without GTM. Events will only be sent to the Contextual backend.
No additional configuration required - it works out of the box!
API Reference
Vanilla JavaScript Functions
All functions from @getcontextual/shopify-sdk/vanilla:
init(config: VanillaShopifyConfig)- Initialize trackergetTracker()- Get tracker instancetrackPageView()- Track page viewtrackProductView(productVariant)- Track product viewtrackCollectionView(collection)- Track collection viewtrackAddToCart(cartLine)- Track add to carttrackCheckoutStarted(checkout)- Track checkout startedtrackCheckoutCompleted(checkout)- Track checkout completedtrackSearch(query, results)- Track searchidentify(userProperties)- Identify user
React Hooks
From @getcontextual/shopify-sdk/react:
useTracker()- Access all tracker functions from context
React Components
From @getcontextual/shopify-sdk/react:
<TrackerProvider config={config}>- Provider component<ProductTracking productVariant={variant} />- Track product views<CollectionTracking collection={collection} />- Track collection views<AddToCartTracking cartLine={cartLine} />- Track add to cart<CheckoutStartedTracking checkout={checkout} />- Track checkout started<CheckoutCompletedTracking checkout={checkout} />- Track checkout completed<IdentifyTracking userProperties={props} />- Identify user<PageViewTracker pathname={pathname} />- Track page views<SearchTracking search={search}, searchResult={searchResult}>- Track search results
Examples
Complete Vanilla JS Example
import {
init,
trackProductView,
trackAddToCart,
identify,
} from "@getcontextual/shopify-sdk/vanilla";
// Initialize
init({
merchantId: "my-store.myshopify.com",
autoTrackPageView: true,
gtmContainerId: "GTM-XXXXXXX",
});
// Product page
function onProductPageLoad(product) {
trackProductView(product.variant);
}
// Add to cart button
function onAddToCart(cartLine) {
trackAddToCart(cartLine);
}
// User login
function onUserLogin(user) {
identify({
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
});
}Complete React Example
import {
TrackerProvider,
useTracker,
ProductTracking,
} from "@getcontextual/shopify-sdk/react";
function App() {
return (
<TrackerProvider config={{ merchantId: "my-store.myshopify.com" }}>
<Router>
<Routes>
<Route path="/products/:id" element={<ProductPage />} />
<Route path="/cart" element={<CartPage />} />
</Routes>
</Router>
</TrackerProvider>
);
}
function ProductPage() {
const { product } = useLoaderData();
const { trackAddToCart } = useTracker();
return (
<>
<ProductTracking productVariant={product.variant} />
<div>
<h1>{product.title}</h1>
<button onClick={() => trackAddToCart(product.cartLine)}>
Add to Cart
</button>
</div>
</>
);
}Package Exports
@getcontextual/shopify-sdk- Core tracker class and types@getcontextual/shopify-sdk/vanilla- Vanilla JavaScript API@getcontextual/shopify-sdk/react- React hooks and components
Troubleshooting
Events Not Being Tracked
- Ensure
init()or<TrackerProvider>is called before tracking - Check browser console for initialization warnings
- Verify
merchantIdmatches your Shopify store domain - If using SSR, ensure tracking is called in
useEffector after hydration
GTM Events Not Appearing
- Verify
gtmContainerIdis correct - Inspect
window.dataLayerin browser console - Use GTM preview mode to see if events are received
- Check Network tab for GTM script requests
TypeScript Errors
- Ensure TypeScript version is compatible (^5.9.2)
- Use the correct import path for your framework
- Types are automatically included - no additional setup needed
React Strict Mode
The SDK is React Strict Mode safe. You may see duplicate event tracking in development mode, but this won't happen in production.
License
Licensed under the Apache License, Version 2.0. See LICENSE for details.
Support
For issues, questions, or contributions, please open an issue on the repository.
