@getcontextual/shopify-sdk 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,652 @@
1
+ # @getcontextual/shopify-sdk
2
+
3
+ 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.
4
+
5
+ ## Features
6
+
7
+ - 🚀 **Framework Agnostic**: Works with vanilla JS, React, and SSR frameworks like Next.js
8
+ - 🔒 **SSR-Safe**: All tracking methods gracefully handle server-side rendering
9
+ - 📦 **Automatic GTM**: Zero-configuration Google Tag Manager integration
10
+ - 📊 **Comprehensive Events**: Product views, collections, cart, checkout, search, and more
11
+ - 🎨 **TypeScript**: Full TypeScript support with Shopify type definitions
12
+ - âš¡ **Auto-Flush**: Automatic event batching for optimal performance
13
+ - 🔄 **React Safe**: Handles React Strict Mode and SSR correctly
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @getcontextual/shopify-sdk
19
+ # or
20
+ pnpm add @getcontextual/shopify-sdk
21
+ # or
22
+ yarn add @getcontextual/shopify-sdk
23
+ ```
24
+
25
+ **Peer Dependencies:** React (≥16.8.0) is optional and only needed if using React components.
26
+
27
+ ## Quick Start
28
+
29
+ ### Vanilla JavaScript
30
+
31
+ ```javascript
32
+ import { init, trackProductView } from "@getcontextual/shopify-sdk/vanilla";
33
+
34
+ // Initialize once
35
+ init({
36
+ merchantId: "your-store.myshopify.com",
37
+ autoTrackPageView: true,
38
+ gtmContainerId: "GTM-XXXXXXX", // Optional
39
+ });
40
+
41
+ // Track events
42
+ trackProductView(productVariant);
43
+ ```
44
+
45
+ ### React
46
+
47
+ ```tsx
48
+ import { TrackerProvider, useTracker } from "@getcontextual/shopify-sdk/react";
49
+
50
+ function App() {
51
+ return (
52
+ <TrackerProvider config={{ merchantId: "your-store.myshopify.com" }}>
53
+ <ProductPage product={product} />
54
+ </TrackerProvider>
55
+ );
56
+ }
57
+
58
+ function ProductPage({ product }) {
59
+ const { trackProductView } = useTracker();
60
+
61
+ useEffect(() => {
62
+ trackProductView(product.variant);
63
+ }, [product.variant, trackProductView]);
64
+
65
+ return <div>{product.title}</div>;
66
+ }
67
+ ```
68
+
69
+ ## Vanilla JavaScript API
70
+
71
+ Perfect for static sites, Vue.js, Svelte, or any framework without React.
72
+
73
+ ### Initialization
74
+
75
+ ```javascript
76
+ import { init } from "@getcontextual/shopify-sdk/vanilla";
77
+
78
+ init({
79
+ merchantId: "your-store.myshopify.com", // Required
80
+ autoTrackPageView: true, // Automatically track page views
81
+ gtmContainerId: "GTM-XXXXXXX", // Optional: GTM container ID
82
+ autoFlush: true, // Auto-flush events (default: true)
83
+ flushInterval: 100, // Flush interval in ms (default: 100)
84
+ });
85
+ ```
86
+
87
+ ### Tracking Events
88
+
89
+ ```javascript
90
+ import {
91
+ trackPageView,
92
+ trackProductView,
93
+ trackCollectionView,
94
+ trackAddToCart,
95
+ trackCheckoutStarted,
96
+ trackCheckoutCompleted,
97
+ trackSearch,
98
+ identify,
99
+ } from "@getcontextual/shopify-sdk/vanilla";
100
+
101
+ // Track page view
102
+ trackPageView();
103
+
104
+ // Track product view
105
+ trackProductView(productVariant);
106
+
107
+ // Track collection view
108
+ trackCollectionView(collection);
109
+
110
+ // Track add to cart
111
+ trackAddToCart(cartLine);
112
+ // Track checkout
113
+ trackCheckoutStarted(checkout);
114
+ trackCheckoutCompleted(checkout);
115
+
116
+ // Track search
117
+ trackSearch(query, searchResults);
118
+
119
+ // Identify user
120
+ identify({
121
+ email: "user@example.com",
122
+ phone: "+1234567890",
123
+ firstName: "John",
124
+ lastName: "Doe",
125
+ });
126
+ ```
127
+
128
+ ## React API
129
+
130
+ Uses React Context for dependency injection, making it easy to access tracking functions throughout your app.
131
+
132
+ ### Setup
133
+
134
+ ```tsx
135
+ import { TrackerProvider } from "@getcontextual/shopify-sdk/react";
136
+
137
+ function App() {
138
+ return (
139
+ <TrackerProvider
140
+ config={{
141
+ merchantId: "your-store.myshopify.com",
142
+ gtmContainerId: "GTM-XXXXXXX", // Optional
143
+ autoFlush: true,
144
+ flushInterval: 100,
145
+ }}
146
+ >
147
+ <YourApp />
148
+ </TrackerProvider>
149
+ );
150
+ }
151
+ ```
152
+
153
+ ### Using Hooks
154
+
155
+ ```tsx
156
+ import { useTracker } from "@getcontextual/shopify-sdk/react";
157
+
158
+ function ProductPage({ product }) {
159
+ const { trackProductView } = useTracker();
160
+
161
+ useEffect(() => {
162
+ trackProductView(product.variant);
163
+ }, [product.variant, trackProductView]);
164
+
165
+ return <div>{product.title}</div>;
166
+ }
167
+
168
+ function AddToCartButton({ cartLine }) {
169
+ const { trackAddToCart } = useTracker();
170
+
171
+ const handleClick = () => {
172
+ // Your add to cart logic
173
+ trackAddToCart(cartLine);
174
+ };
175
+
176
+ return <button onClick={handleClick}>Add to Cart</button>;
177
+ }
178
+ ```
179
+
180
+ ### Using Components
181
+
182
+ For automatic tracking when components mount:
183
+
184
+ ```tsx
185
+ import {
186
+ ProductTracking,
187
+ CollectionTracking,
188
+ AddToCartTracking,
189
+ CheckoutStartedTracking,
190
+ CheckoutCompletedTracking,
191
+ IdentifyTracking,
192
+ PageViewTracker,
193
+ } from "@getcontextual/shopify-sdk/react";
194
+
195
+ function ProductPage({ product }) {
196
+ return (
197
+ <>
198
+ <ProductTracking productVariant={product.variant} />
199
+ <h1>{product.title}</h1>
200
+ </>
201
+ );
202
+ }
203
+
204
+ function CheckoutPage({ checkout }) {
205
+ return (
206
+ <>
207
+ <CheckoutStartedTracking checkout={checkout} />
208
+ {/* Your checkout UI */}
209
+ </>
210
+ );
211
+ }
212
+ ```
213
+
214
+ ## Nextjs/Server-Side Rendering (SSR) Guide
215
+
216
+ All tracking methods are SSR-safe and will gracefully no-op on the server-side:
217
+
218
+ - ✅ Safe to call tracking methods during SSR
219
+ - ✅ No errors or warnings on the server
220
+ - ✅ Events only tracked on the client-side after hydration
221
+ - ✅ Works with Next.js, Remix, and other SSR frameworks
222
+
223
+ ### Initialize Tracker
224
+
225
+ Create a client component to initialize the tracker:
226
+
227
+ ```tsx
228
+ "use client";
229
+
230
+ import { useEffect, useContext, createContext } from "react";
231
+ import {
232
+ init,
233
+ trackPageView,
234
+ trackProductView,
235
+ trackCollectionView,
236
+ trackAddToCart,
237
+ trackCheckoutStarted,
238
+ trackCheckoutCompleted,
239
+ trackSearch,
240
+ identify,
241
+ } from "@getcontextual/shopify-sdk/vanilla";
242
+
243
+ import type { HeadlessShopifyConfig } from "../tracker";
244
+
245
+ interface TrackerContextValue {
246
+ trackPageView: typeof trackPageView;
247
+ trackProductView: typeof trackProductView;
248
+ trackCollectionView: typeof trackCollectionView;
249
+ trackAddToCart: typeof trackAddToCart;
250
+ trackCheckoutStarted: typeof trackCheckoutStarted;
251
+ trackCheckoutCompleted: typeof trackCheckoutCompleted;
252
+ trackSearch: typeof trackSearch;
253
+ identify: typeof identify;
254
+ }
255
+
256
+ const TrackerContext = createContext<TrackerContextValue | null>(null);
257
+
258
+ export function TrackerProvider({
259
+ merchantId: string,
260
+ }: {
261
+ merchantId: string;
262
+ }) {
263
+ useEffect(() => {
264
+ init({ merchantId, autoTrackPageView: false });
265
+ }, [config]);
266
+
267
+ const value: TrackerContextValue = {
268
+ trackPageView,
269
+ trackProductView,
270
+ trackCollectionView,
271
+ trackAddToCart,
272
+ trackSearch,
273
+ trackCheckoutStarted,
274
+ trackCheckoutCompleted,
275
+ identify,
276
+ };
277
+
278
+ return (
279
+ <TrackerContext.Provider value={value}>{children}</TrackerContext.Provider>
280
+ );
281
+ }
282
+
283
+ export function useTracker() {
284
+ const context = useContext(TrackerContext);
285
+ if (!context) {
286
+ throw new Error("useTracker must be used within a TrackerProvider");
287
+ }
288
+ return context;
289
+ }
290
+ ```
291
+
292
+ ### Automatic Page View Tracker
293
+
294
+ #### With Page Router
295
+
296
+ ```tsx
297
+ // components/page-view-tracker.tsx
298
+ "use client";
299
+
300
+ import { useEffect } from "react";
301
+ import { useRouter } from "next/router";
302
+ // contextual sdk is global singleton so can be accessed like this
303
+ import { trackPageView } from "@getcontextual/shopify-sdk/vanilla";
304
+
305
+ export function PageViewTracker() {
306
+ const router = useRouter();
307
+
308
+ useEffect(() => {
309
+ const handleRouteChange = () => {
310
+ void trackPageView();
311
+ };
312
+
313
+ router.events.on("routeChangeComplete", handleRouteChange);
314
+
315
+ return () => {
316
+ router.events.off("routeChangeComplete", handleRouteChange);
317
+ };
318
+ }, [router.events]);
319
+
320
+ return null;
321
+ }
322
+ ```
323
+
324
+ #### With App Router
325
+
326
+ ```tsx
327
+ "use client";
328
+ // contextual sdk is global singleton so can be accessed like this
329
+ import { useEffect, Suspense } from "react";
330
+ import { usePathname, useSearchParams } from "next/navigation";
331
+ import { useTracker } from "./tracker-provider";
332
+
333
+ function PageViewHandler() {
334
+ const pathname = usePathname();
335
+ const searchParams = useSearchParams();
336
+ const { trackPageView } = useTracker();
337
+
338
+ useEffect(() => {
339
+ trackPageView();
340
+ }, [pathname, searchParams, trackPageView]);
341
+
342
+ return null;
343
+ }
344
+
345
+ export function AppRouterPageViewTracker() {
346
+ return (
347
+ <Suspense fallback={null}>
348
+ <PageViewHandler />
349
+ </Suspense>
350
+ );
351
+ }
352
+ ```
353
+
354
+ ```tsx
355
+ // app/layout.tsx
356
+
357
+ export default function RootLayout({ children }) {
358
+ return (
359
+ <html>
360
+ <body>
361
+ <TrackerProvider config={{ merchantId: "my-store.myshopify.com" }}>
362
+ <AppRouterPageViewTracker />
363
+ {children}
364
+ </TrackerProvider>
365
+ </body>
366
+ </html>
367
+ );
368
+ }
369
+ ```
370
+
371
+ ### Using Components
372
+
373
+ ```tsx
374
+ "use client";
375
+
376
+ import { trackProductView } from "@getcontextual/shopify-sdk/vanilla";
377
+
378
+ export function ProductTracking({
379
+ productVariant,
380
+ }: {
381
+ productVariant: ShopifyProductVariant;
382
+ }) {
383
+ const hasTracked = useRef(false);
384
+
385
+ useEffect(() => {
386
+ if (!hasTracked.current && productVariant) {
387
+ hasTracked.current = true;
388
+ void trackProductView(productVariant);
389
+ }
390
+ }, [productVariant]);
391
+
392
+ return null;
393
+ }
394
+ ```
395
+
396
+ ### Using Hooks
397
+
398
+ ```tsx
399
+ // pages/products/[id].tsx
400
+
401
+ import { useEffect } from "react";
402
+ import { trackProductView } from "@getcontextual/shopify-sdk/vanilla";
403
+
404
+ export default function ProductPage({ product }) {
405
+ useEffect(() => {
406
+ if (product?.variant) {
407
+ void trackProductView(product.variant);
408
+ }
409
+ }, [product]);
410
+
411
+ return <div>{product.title}</div>;
412
+ }
413
+ ```
414
+
415
+ ##W Key Points
416
+
417
+ 1. **Always use `'use client'`**: Any file that imports from `@getcontextual/shopify-sdk` must have `'use client'` at the top
418
+ 2. **SSR-safe**: All tracking functions are SSR-safe and will no-op on the server
419
+ 3. **Initialize once**: Call `init()` only once, typically in a provider component
420
+ 4. **Track in useEffect**: Always track events inside `useEffect` to ensure they run on the client-side
421
+ 5. **Page views**: Track page views when route changes (App Router: `usePathname`/`useSearchParams`, Pages Router: `router.events`)
422
+
423
+ ## Configuration
424
+
425
+ ### HeadlessShopifyConfig
426
+
427
+ ```typescript
428
+ interface HeadlessShopifyConfig {
429
+ merchantId: string; // Required: Your Shopify store domain (e.g., 'store.myshopify.com')
430
+ version?: string; // Optional: SDK version (default: '1.0.0')
431
+ autoFlush?: boolean; // Optional: Auto-flush events (default: true)
432
+ flushInterval?: number; // Optional: Flush interval in milliseconds (default: 100)
433
+ gtmContainerId?: string; // Optional: Google Tag Manager container ID
434
+ }
435
+ ```
436
+
437
+ ### VanillaShopifyConfig
438
+
439
+ Extends `HeadlessShopifyConfig` with:
440
+
441
+ ```typescript
442
+ interface VanillaShopifyConfig extends HeadlessShopifyConfig {
443
+ autoTrackPageView: boolean; // Automatically track page views on init
444
+ }
445
+ ```
446
+
447
+ ## TypeScript Support
448
+
449
+ The package is fully typed. All Shopify types are based on `@shopify/web-pixels-extension` for consistency.
450
+
451
+ ```typescript
452
+ import type {
453
+ ShopifyProductVariant,
454
+ ShopifyCartLine,
455
+ ShopifyCheckout,
456
+ ShopifyCollection,
457
+ ShopifySearchResult,
458
+ HeadlessShopifyConfig,
459
+ } from "@getcontextual/shopify-sdk";
460
+ ```
461
+
462
+ ## Google Tag Manager Integration
463
+
464
+ ### Setup
465
+
466
+ ```typescript
467
+ init({
468
+ merchantId: "your-store.myshopify.com",
469
+ gtmContainerId: "GTM-XXXXXXX", // Your GTM container ID
470
+ });
471
+ ```
472
+
473
+ ### How It Works
474
+
475
+ 1. **Automatic Loading**: GTM script loads automatically on tracker initialization
476
+ 2. **Event Pushing**: All events are pushed to `window.dataLayer`
477
+ 3. **Event Format**: Events follow standard GTM data layer format
478
+
479
+ ### Event Structure
480
+
481
+ Events are pushed to `dataLayer` as:
482
+
483
+ ```javascript
484
+ {
485
+ event: 'product_viewed',
486
+ event_data: {
487
+ merchant_id: 'your-store.myshopify.com',
488
+ event_id: 'unique-event-id',
489
+ // ... product data, user data, etc.
490
+ }
491
+ }
492
+ ```
493
+
494
+ **Note:** If you don't provide a `gtmContainerId`, the SDK works perfectly without GTM. Events will only be sent to the Contextual backend.
495
+
496
+ No additional configuration required - it works out of the box!
497
+
498
+ ## API Reference
499
+
500
+ ### Vanilla JavaScript Functions
501
+
502
+ All functions from `@getcontextual/shopify-sdk/vanilla`:
503
+
504
+ - `init(config: VanillaShopifyConfig)` - Initialize tracker
505
+ - `getTracker()` - Get tracker instance
506
+ - `trackPageView()` - Track page view
507
+ - `trackProductView(productVariant)` - Track product view
508
+ - `trackCollectionView(collection)` - Track collection view
509
+ - `trackAddToCart(cartLine)` - Track add to cart
510
+ - `trackCheckoutStarted(checkout)` - Track checkout started
511
+ - `trackCheckoutCompleted(checkout)` - Track checkout completed
512
+ - `trackSearch(query, results)` - Track search
513
+ - `identify(userProperties)` - Identify user
514
+
515
+ ### React Hooks
516
+
517
+ From `@getcontextual/shopify-sdk/react`:
518
+
519
+ - `useTracker()` - Access all tracker functions from context
520
+
521
+ ### React Components
522
+
523
+ From `@getcontextual/shopify-sdk/react`:
524
+
525
+ - `<TrackerProvider config={config}>` - Provider component
526
+ - `<ProductTracking productVariant={variant} />` - Track product views
527
+ - `<CollectionTracking collection={collection} />` - Track collection views
528
+ - `<AddToCartTracking cartLine={cartLine} />` - Track add to cart
529
+ - `<CheckoutStartedTracking checkout={checkout} />` - Track checkout started
530
+ - `<CheckoutCompletedTracking checkout={checkout} />` - Track checkout completed
531
+ - `<IdentifyTracking userProperties={props} />` - Identify user
532
+ - `<PageViewTracker pathname={pathname} />` - Track page views
533
+ - `<SearchTracking search={search}, searchResult={searchResult}>` - Track search results
534
+
535
+ ## Examples
536
+
537
+ ### Complete Vanilla JS Example
538
+
539
+ ```javascript
540
+ import {
541
+ init,
542
+ trackProductView,
543
+ trackAddToCart,
544
+ identify,
545
+ } from "@getcontextual/shopify-sdk/vanilla";
546
+
547
+ // Initialize
548
+ init({
549
+ merchantId: "my-store.myshopify.com",
550
+ autoTrackPageView: true,
551
+ gtmContainerId: "GTM-XXXXXXX",
552
+ });
553
+
554
+ // Product page
555
+ function onProductPageLoad(product) {
556
+ trackProductView(product.variant);
557
+ }
558
+
559
+ // Add to cart button
560
+ function onAddToCart(cartLine) {
561
+ trackAddToCart(cartLine);
562
+ }
563
+
564
+ // User login
565
+ function onUserLogin(user) {
566
+ identify({
567
+ email: user.email,
568
+ firstName: user.firstName,
569
+ lastName: user.lastName,
570
+ });
571
+ }
572
+ ```
573
+
574
+ ### Complete React Example
575
+
576
+ ```tsx
577
+ import {
578
+ TrackerProvider,
579
+ useTracker,
580
+ ProductTracking,
581
+ } from "@getcontextual/shopify-sdk/react";
582
+
583
+ function App() {
584
+ return (
585
+ <TrackerProvider config={{ merchantId: "my-store.myshopify.com" }}>
586
+ <Router>
587
+ <Routes>
588
+ <Route path="/products/:id" element={<ProductPage />} />
589
+ <Route path="/cart" element={<CartPage />} />
590
+ </Routes>
591
+ </Router>
592
+ </TrackerProvider>
593
+ );
594
+ }
595
+
596
+ function ProductPage() {
597
+ const { product } = useLoaderData();
598
+ const { trackAddToCart } = useTracker();
599
+
600
+ return (
601
+ <>
602
+ <ProductTracking productVariant={product.variant} />
603
+ <div>
604
+ <h1>{product.title}</h1>
605
+ <button onClick={() => trackAddToCart(product.cartLine)}>
606
+ Add to Cart
607
+ </button>
608
+ </div>
609
+ </>
610
+ );
611
+ }
612
+ ```
613
+
614
+ ## Package Exports
615
+
616
+ - **`@getcontextual/shopify-sdk`** - Core tracker class and types
617
+ - **`@getcontextual/shopify-sdk/vanilla`** - Vanilla JavaScript API
618
+ - **`@getcontextual/shopify-sdk/react`** - React hooks and components
619
+
620
+ ## Troubleshooting
621
+
622
+ ### Events Not Being Tracked
623
+
624
+ 1. Ensure `init()` or `<TrackerProvider>` is called before tracking
625
+ 2. Check browser console for initialization warnings
626
+ 3. Verify `merchantId` matches your Shopify store domain
627
+ 4. If using SSR, ensure tracking is called in `useEffect` or after hydration
628
+
629
+ ### GTM Events Not Appearing
630
+
631
+ 1. Verify `gtmContainerId` is correct
632
+ 2. Inspect `window.dataLayer` in browser console
633
+ 3. Use GTM preview mode to see if events are received
634
+ 4. Check Network tab for GTM script requests
635
+
636
+ ### TypeScript Errors
637
+
638
+ 1. Ensure TypeScript version is compatible (^5.9.2)
639
+ 2. Use the correct import path for your framework
640
+ 3. Types are automatically included - no additional setup needed
641
+
642
+ ### React Strict Mode
643
+
644
+ The SDK is React Strict Mode safe. You may see duplicate event tracking in development mode, but this won't happen in production.
645
+
646
+ ## License
647
+
648
+ Licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE) for details.
649
+
650
+ ## Support
651
+
652
+ For issues, questions, or contributions, please open an issue on the repository.
package/THIRD_PARTY ADDED
@@ -0,0 +1,43 @@
1
+ This package bundles the following third-party dependencies from @contextual/browser:
2
+
3
+ ## js-cookie
4
+ License: MIT
5
+ Copyright: Copyright (c) 2014-2017 Klaus Hartl, Fagner Brack
6
+ https://github.com/js-cookie/js-cookie
7
+
8
+ ## uuid
9
+ License: MIT
10
+ Copyright: Copyright (c) 2010-2020 Robert Kieffer and other contributors
11
+ https://github.com/uuidjs/uuid
12
+
13
+ ## tldts
14
+ License: MIT
15
+ Copyright: Copyright (c) 2017 Thomas Parisot, 2018 Rémi Berson
16
+ https://github.com/remusao/tldts
17
+
18
+ ## ua-parser-js
19
+ License: MIT
20
+ Copyright: Copyright (c) 2012-2021 Faisal Salman <f@faisalman.com>
21
+ https://github.com/faisalman/ua-parser-js
22
+
23
+ ## detectincognitojs
24
+ License: MIT
25
+ Copyright: Copyright (c) 2020-present Jacek Laskowski
26
+ https://github.com/JLaskowski/detectincognitojs
27
+
28
+ ## is-email
29
+ License: MIT
30
+ Copyright: Copyright (c) 2020-present
31
+ https://github.com/hapijs/isemail
32
+
33
+ ## is-network-error
34
+ License: MIT
35
+ Copyright: Copyright (c) 2019 Sindre Sorhus
36
+ https://github.com/sindresorhus/is-network-error
37
+
38
+ ---
39
+
40
+ Full license texts for these packages can be found in their respective npm packages:
41
+ - npm view <package-name> license
42
+ - Or visit: https://www.npmjs.com/package/<package-name>
43
+
@@ -0,0 +1,7 @@
1
+ export { HeadlessShopifyTracker } from "./tracker";
2
+ export type { HeadlessShopifyConfig } from "./tracker";
3
+ export type * from "./types";
4
+ export { Tracking } from "@contextual/browser";
5
+ export type { BrowserEvent } from "@contextual/browser";
6
+ export * from "./react";
7
+ export * from "./vanilla";