npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@polar-analytics/pixel-sdk

v0.18.0

Published

A JavaScript/TypeScript SDK for reporting customer touchpoints to Polar Analytics

Readme

Pixel SDK

A lightweight JavaScript utility for reporting user touchpoints, similar to the Polar Analytics Pixel.

Contents

Installation

Install via npm (recommended for Next.js, Hydrogen, Remix, Vite, any bundled app):

npm install @polar-analytics/pixel-sdk

Loading via <script> (non-npm sites)

If you can't run a bundler on your site (plain HTML, Webflow, Wix, WordPress, a headless CMS that only lets you paste <script> tags), load the SDK's IIFE bundle from unpkg and use the polarPixel global:

<script src="https://unpkg.com/@polar-analytics/[email protected]/dist/index.global.js"></script>
<script>
  const { generatePayloadBase, sendPixelEvent, eventFactory } = polarPixel;
  // …same API as the npm import, just accessed off the global.
</script>

Pin to an exact version (@0.17.0) in production rather than @latest — that way a new SDK release can't silently change behavior on your site.

Example usage

Every event payload has three "common" fields — two required, one optional:

| Field | Required? | What to pass | | ------------------ | --------- | ----------------------------------------------------------------------------------------------------------------- | | shopifyEventId | Yes | A unique UUID per event. Use crypto.randomUUID() in the browser. | | shopifyShopURL | Yes | The store's *.myshopify.com domain (e.g. my-store.myshopify.com). This is the Shopify store domain, not the customer-facing storefront URL or a theme page. | | shopifyClientId | No | Shopify's anonymous visitor id. Omit it if you don't have one (e.g. any non-Shopify-theme environment). The SDK will automatically fall back to the anonymous userId it persists in localStorage via generatePayloadBase(). |

Headless / non-Shopify-theme integration? Jump to Headless Shopify Integration — you can safely skip shopifyClientId.

import {
  generatePayloadBase,
  sendPixelEvent,
  eventFactory,
} from "@polar-analytics/pixel-sdk";

// 1. Build the payload base. This reads URL, referrer, title, userAgent, etc.
//    from window.document/navigator and persists an anonymous userId + sessionId
//    in localStorage.
const payloadBase = await generatePayloadBase({
  // Optional — include only if you already know who the customer is.
  // customer: {
  //   id: <variable-containing-customer-id>,
  //   email: <variable-containing-customer-email>,
  // },
});

// 2. Required: the store's myshopify.com domain.
const shopifyShopURL = "my-store.myshopify.com";

// 3. Build the event-specific payload with the matching eventFactory.
const data = eventFactory.cart_viewed({
  shopifyEventId: crypto.randomUUID(), // required, must be unique per event
  shopifyShopURL,                      // required
  // for the client_id
  // Please refer to https://community.shopify.com/c/shopify-apps/web-pixel-clientid/m-p/2664271/highlight/true#M80886
  // As a fallback you can also use payloadBase.data.user_id
  // shopifyClientId: "<optional>",
});

// 4. Send to the Polar pixel endpoint (provided by Polar Analytics).
sendPixelEvent(
  pixelEndpoint,
  payloadBase,
  { data },
);

When should I set shopifyClientId explicitly? Only inside a Shopify Web Pixel extension where Shopify hands you a real clientId (Shopify community thread). Anywhere else — headless storefronts, Hydrogen, custom Next.js/React apps — leave it out.

Sending a custom event

Use eventFactory.custom only for events not covered by the standard eventFactory.* helpers — using it for a standard event will drop event-specific data on the floor.

const data = eventFactory.custom({
  shopifyEventName: "<the-name-of-the-custom-event>",
  shopifyEventId: crypto.randomUUID(),
  shopifyShopURL,
});

(Optional) With callbacks and retries

sendPixelEvent retries failed requests with exponential backoff (default: 3 retries) and returns a SendPixelEventResult.

const result = await sendPixelEvent(
  pixelEndpoint,
  payloadBase,
  { data },
  {
    maxRetries: 5,
    onSuccess: (response) => {
      console.log("Event delivered", response.status);
    },
    onError: (error, attempts) => {
      console.error(`Failed after ${attempts} attempts`, error);
    },
    onRetry: (attempt, error, nextDelayMs) => {
      console.warn(`Retry ${attempt} in ${nextDelayMs}ms`);
    },
  },
);

if (!result.success) {
  // result.error and result.attempts are available
}

(Optional) With sendBeacon (page unload)

Use useBeacon: true for page unload / visibilitychange scenarios. When the beacon fires successfully the function returns immediately with { success: true, usedBeacon: true }onSuccess/onError callbacks are not invoked. If the beacon fails, the SDK falls back to fetch with retries.

sendPixelEvent(pixelEndpoint, payloadBase, { data }, { useBeacon: true });

Headless Shopify Integration

Why this guide exists

On a standard Shopify Liquid storefront, the Polar app installs a Web Pixel that fires events automatically — you don't write any SDK code. On a headless storefront (React, Next.js, Hydrogen, Remix, a custom storefront, a mobile app, or any site not served by Shopify's theme engine) there is no Web Pixel sandbox and no auto-install, so Polar can't inject the script or read Shopify's event context. You install the SDK manually and call it from your own route and cart code. This guide shows a developer what to pass, how to identify visitors, and how to verify events are arriving.

If you have a site manager or developer on your team, these instructions are intended for them.

What you need to provide

| Field | Required? | How to source it in headless | | ------------------ | --------- | ---------------------------------------------------------------------------------------------------------------- | | shopifyEventId | Yes | Generate a fresh UUID per event: crypto.randomUUID() (available in all modern browsers and Node ≥ 19). | | shopifyShopURL | Yes | Your store's *.myshopify.com domain. This is your admin-level store domain (e.g. my-store.myshopify.com), not the customer-facing storefront URL, and not a theme page URL. You can hard-code it or read it from an env var. | | shopifyClientId | No | Skip it in headless. Shopify's clientId is only exposed inside the Web Pixel sandbox, which you don't have. The SDK will automatically use the anonymous userId it persists in localStorage via generatePayloadBase(). |

Anything else (URL, referrer, page title, userAgent, UTM parameters, marketing cookies) is read for you by generatePayloadBase() from window.document / navigator — you do not pass those manually.

Minimal page_viewed example in a headless storefront

import {
  generatePayloadBase,
  sendPixelEvent,
  eventFactory,
} from "@polar-analytics/pixel-sdk";

const SHOP_URL = "my-store.myshopify.com"; // your *.myshopify.com domain

async function trackPageView() {
  const payloadBase = await generatePayloadBase({
    // Pass the customer only if your app already knows who is logged in.
    // customer: { id: user?.id, email: user?.email },
  });

  const data = eventFactory.page_viewed({
    shopifyEventId: crypto.randomUUID(), // required — fresh UUID per event
    shopifyShopURL: SHOP_URL,            // required
    // for the client_id
    // Please refer to https://community.shopify.com/c/shopify-apps/web-pixel-clientid/m-p/2664271/highlight/true#M80886
    // As a fallback you can also use payloadBase.data.user_id
    // shopifyClientId: "<optional>",
  });

  await sendPixelEvent(pixelEndpoint, payloadBase, { data });
}

Identifying the visitor

When a visitor logs in, signs up, or submits a form with their contact info, pass their id and/or email through generatePayloadBase(). Polar uses this to stitch their anonymous pre-login sessions to their identified sessions in the customer journey.

// Call this once right after login / signup / form submission.
// Subsequent events in the same session will carry the customer identity.
const payloadBase = await generatePayloadBase({
  customer: {
    id: user.id,       // your internal customer id, if you have one
    email: user.email, // at least one of { id, email } should be set
  },
});

// Re-send any event you want tied to the identified session, e.g. a
// post-login page_viewed:
const data = eventFactory.page_viewed({
  shopifyEventId: crypto.randomUUID(),
  shopifyShopURL: SHOP_URL,
});
await sendPixelEvent(pixelEndpoint, payloadBase, { data });

Before login, generatePayloadBase() persists an anonymous userId in localStorage; Polar uses that to stitch the pre-login activity to the identified customer the first time you pass customer.id / customer.email.

Minimal headless payloads by event

The full API Reference below documents every field Shopify's schema defines. In a headless integration you usually need only a small subset. The snippets below show the common shape — enough to get attribution and revenue tracking working. They are illustrative (some nested Shopify types are abbreviated); consult the matching entry in the API Reference for every required field when you build the real payload.

eventFactory.page_viewed({
  shopifyEventId: crypto.randomUUID(),
  shopifyShopURL: SHOP_URL,
  shopifyPageType: "home", // optional but recommended: "home" | "product" | "collection" | "cart" | "checkout" | ...
});
eventFactory.product_viewed({
  shopifyEventId: crypto.randomUUID(),
  shopifyShopURL: SHOP_URL,
  shopifyPageDetail: product.handle,
  shopifyPageProductId: product.id, // Shopify product id (gid or numeric)
  shopifyPageType: "product",
  shopifyEventData: {
    productVariant: {
      id: variant.id,
      sku: variant.sku,
      title: variant.title,
      untranslatedTitle: variant.title,
      image: { src: variant.imageUrl ?? null },
      price: { amount: variant.price, currencyCode: variant.currencyCode },
      product: {
        id: product.id,
        title: product.title,
        untranslatedTitle: product.title,
        type: product.type ?? null,
        url: product.url ?? null,
        vendor: product.vendor,
      },
    },
  },
});
eventFactory.product_added_to_cart({
  shopifyEventId: crypto.randomUUID(),
  shopifyShopURL: SHOP_URL,
  shopifyEventData: {
    cartLine: {
      quantity,
      cost: {
        totalAmount: { amount: lineTotal, currencyCode },
      },
      merchandise: {
        id: variant.id,
        sku: variant.sku,
        title: variant.title,
        price: { amount: variant.price, currencyCode },
        product: {
          id: product.id,
          title: product.title,
          vendor: product.vendor,
        },
      },
    },
  },
});
eventFactory.checkout_started({
  shopifyEventId: crypto.randomUUID(),
  shopifyShopURL: SHOP_URL,
  shopifyEventData: {
    checkout: {
      token: checkoutId,
      currencyCode,
      totalPrice: { amount: cartTotal, currencyCode },
      email: customer.email ?? null,
      lineItems: cart.lines.map((line) => ({
        id: line.id,
        quantity: line.quantity,
        variant: {
          id: line.variant.id,
          sku: line.variant.sku,
          price: { amount: line.variant.price, currencyCode },
          product: { id: line.product.id, title: line.product.title },
        },
      })),
    },
  },
});

checkout_completed has more required top-level fields than the other events — Polar uses these directly for revenue attribution and reconciliation, so don't skip any.

eventFactory.checkout_completed({
  shopifyEventId: crypto.randomUUID(),
  shopifyShopURL: SHOP_URL,
  shopifyPageType: "thank_you",
  shopifyOrderId: order.id,
  shopifyCustomerEmail: order.email,
  shopifyOrderSubtotalPrice: order.subtotal,
  shopifyOrderTotalPrice: order.total,
  shopifyOrderCurrency: order.currencyCode,
  shopifyOrderLineItems: order.lineItems.map((line) => ({
    lineItemId: line.id,
    lineItemProductId: line.product.id,
    lineItemTitle: line.title,
    lineItemVariantId: line.variant.id,
  })),
  shopifyEventData: {
    checkout: {
      token: order.id,
      order: { id: order.id },
      currencyCode: order.currencyCode,
      totalPrice: { amount: order.total, currencyCode: order.currencyCode },
      email: order.email,
      lineItems: order.lineItems.map(/* …same shape as checkout_started… */),
    },
  },
});

SPA route changes

In single-page apps the browser does not fire a full navigation when the URL changes, so page_viewed will not re-fire automatically. Call trackPageView() from your router's route-change hook.

Next.js App Router (usePathname + useSearchParams):

"use client";
import { usePathname, useSearchParams } from "next/navigation";
import { useEffect } from "react";

export function PolarPageViewTracker() {
  const pathname = usePathname();
  const searchParams = useSearchParams();

  useEffect(() => {
    trackPageView();
  }, [pathname, searchParams]);

  return null;
}

React Router v6 (useLocation):

import { useEffect } from "react";
import { useLocation } from "react-router-dom";

export function PolarPageViewTracker() {
  const location = useLocation();
  useEffect(() => {
    trackPageView();
  }, [location.pathname, location.search]);
  return null;
}

Vue Router:

import { useRouter } from "vue-router";

const router = useRouter();
router.afterEach(() => {
  trackPageView();
});

Plain <script> install without a router — listen for the browser's popstate event and wrap history.pushState:

<script>
  (function () {
    const fire = () => trackPageView();
    window.addEventListener("popstate", fire);
    const origPush = history.pushState;
    history.pushState = function () {
      origPush.apply(this, arguments);
      fire();
    };
  })();
</script>

Server-side deduplication

If you send events from both the client and a server (e.g. a Shopify webhook worker that fires checkout_completed from order webhooks while the client also fires one on the thank-you page), you need to deduplicate the two copies so your analytics counts the conversion once. The SDK does not dedupe for you; your backend decides what counts as a duplicate.

The pattern:

  1. Generate shopifyEventId = crypto.randomUUID() once on the client.
  2. Send the client-side event with that id.
  3. Persist the same id somewhere the server will see it — a Shopify cart / checkout attribute, your own order metadata, or a hidden form field.
  4. When the server fires its copy, reuse the same shopifyEventId.
  5. Your ingestion or warehouse deduplicates by (event_type, shopifyEventId).
// Client
const shopifyEventId = crypto.randomUUID();

await sendPixelEvent(pixelEndpoint, payloadBase, {
  data: eventFactory.checkout_completed({
    shopifyEventId,
    shopifyShopURL: SHOP_URL,
    shopifyEventData: { checkout: { /* … */ } },
  }),
});

// Attach the id to the cart / order so the server can read it back.
await generateCartAttribute(); // existing `polar_attr` flow
await fetch("/orders", {
  method: "POST",
  body: JSON.stringify({ ...orderPayload, polarEventId: shopifyEventId }),
});
// Server (e.g. Shopify order-paid webhook handler)
await sendPixelEvent(pixelEndpoint, serverPayloadBase, {
  data: eventFactory.checkout_completed({
    shopifyEventId: order.polarEventId, // ← reuse, do NOT regenerate
    shopifyShopURL: SHOP_URL,
    shopifyEventData: { checkout: { /* … */ } },
  }),
});

Verify your install

After deploying, verify that events are actually reaching Polar. These three checks catch 90% of install problems:

  1. Open the browser devtools Network tab, filter by your pixel endpoint (pixel-api.polaranalytics.com or your configured endpoint), reload the page. You should see a POST per event with status 200 or 204. Inspect the request payload — shopifyEventId, shopifyShopURL, and user_id must all be set.

  2. Log the payload base in a staging build to sanity-check UTM capture, localStorage userId persistence, and marketing cookies:

    const payloadBase = await generatePayloadBase({ /* … */ });
    console.log("polar payloadBase", payloadBase);
    // Check: payloadBase.data.user_id is set, UTM fields populated if you
    // landed from a tagged URL, shopifyShopURL is your *.myshopify.com domain.
  3. Check the event in Polar — events typically land in Polar's ingestion within seconds. Check your Polar dashboard's pixel event viewer (ask your Polar CSM if you don't know where this is) and filter by the shopifyEventId you just generated. If it's not there, jump to Troubleshooting.

Troubleshooting

Check, in order:

  • Network tab shows no request → the SDK isn't being called. Re-check your route-change hook (SPA) or <head> script placement.
  • Network tab shows request, status is 0 or CORS error → your site's Content Security Policy (connect-src) is blocking the pixel endpoint. Add the Polar pixel hostname to connect-src.
  • Status 200 but events missing in PolarshopifyShopURL is probably wrong (e.g. you passed my-store.com instead of my-store.myshopify.com). Polar matches events to the tenant by the *.myshopify.com domain.

generatePayloadBase() persists the anonymous userId in localStorage. It will not persist if:

  • Safari ITP / third-party context — if your storefront runs in an iframe on another domain, localStorage may be partitioned or wiped after 7 days. Use a first-party subdomain for your storefront.
  • Incognito / private browsinglocalStorage is wiped when the window closes; this is expected.
  • Consent banner blocks storage before first call — if your consent manager gates localStorage until the visitor accepts, nothing persists for refusers. Call generatePayloadBase() only after consent if you need session stitching.

Most common cause: client and server both fire the same event with different shopifyEventIds. See Server-side deduplication — generate the UUID once on the client and have the server reuse it.

Less common: the client fires page_viewed twice on a single SPA route change because the route-change hook runs twice (React Strict Mode in dev, or a router that fires useEffect on both mount and first navigation). Guard with a ref or compare against the previous pathname.

If you updated your <script> tag and the old one is still loading: purge your CDN cache (Cloudflare, Fastly, Vercel edge). For Shopify-hosted pages, theme edits can take a minute to propagate; hard-reload with devtools open and "Disable cache" checked.

You're on a very old browser or on Node < 19. Polyfill with uuid:

import { v4 as uuidv4 } from "uuid";
const shopifyEventId = typeof crypto !== "undefined" && crypto.randomUUID
  ? crypto.randomUUID()
  : uuidv4();

(Recommended) Cart Attribute Injection

Cart attributes persist tracking data (marketing cookies, UTM params, click IDs) through Shopify checkout, enabling accurate attribution.

The examples below use @shopify/storefront-api-client.

import {
  generateCartAttribute,
  POLAR_ATTR_KEY,
} from "@polar-analytics/pixel-sdk";
import { createStorefrontApiClient } from "@shopify/storefront-api-client";

const client = createStorefrontApiClient({
  storeDomain: "https://your-store.myshopify.com",
  apiVersion: "2025-04",
  publicAccessToken: "your-public-access-token",
});

Creating a cart with polar_attr

const result = await generateCartAttribute();

const { data } = await client.request(
  `mutation cartCreate($input: CartInput!) {
    cartCreate(input: $input) {
      cart { id attributes { key value } }
    }
  }`,
  {
    variables: {
      input: {
        lines: [{ merchandiseId: variantId, quantity: 1 }],
        attributes: [{ key: POLAR_ATTR_KEY, value: result.encoded }],
      },
    },
  },
);

Updating cart attributes (with merge)

When updating an existing cart, pass the current polar_attr value so first-touch attribution data is preserved:

const existingPolarAttr = cart.attributes.find(
  (a) => a.key === POLAR_ATTR_KEY,
)?.value;

const result = await generateCartAttribute({
  existingPolarAttr,
});

await client.request(
  `mutation cartAttributesUpdate($cartId: ID!, $attributes: [AttributeInput!]!) {
    cartAttributesUpdate(cartId: $cartId, attributes: $attributes) {
      cart { id attributes { key value } }
    }
  }`,
  {
    variables: {
      cartId: cart.id,
      attributes: [{ key: POLAR_ATTR_KEY, value: result.encoded }],
    },
  },
);

Best Practices

  • Call generateCartAttribute() on every cart mutation (create, add line, update). This ensures the latest session info is always attached.
  • Always pass existingPolarAttr on cart updates. This preserves first-touch attribution (e.g. the original gclid).

API Reference

Supported Events

eventFactory.page_viewed

The page_viewed event logs an instance where a customer visited a page. This event is available on the online store, checkout, and Order status pages.

Source: Shopify Documentation

eventFactory.product_viewed

The product_viewed event logs an instance where a customer visited a product details page. This event is available on the product page.

Source: Shopify Documentation

eventFactory.collection_viewed

The collection_viewed event logs an instance where a customer visited a product collection index page. This event is available on the online store page.

Source: Shopify Documentation

eventFactory.search_submitted

The search_submitted event logs an instance where a customer performed a search on the storefront. The products returned from the search query are in this event object (the first product variant for each product is listed in the array). This event is available on the online store page.

Source: Shopify Documentation

eventFactory.cart_viewed

The cart_viewed event logs an instance where a customer visited the cart page.

Source: Shopify Documentation

eventFactory.product_added_to_cart

The product_added_to_cart event logs an instance where a customer adds a product to their cart. This event is available on the online store page.

Source: Shopify Documentation

eventFactory.product_removed_from_cart

The product_removed_from_cart event logs an instance where a customer removes a product from their cart. This event is available on the online store page.

Source: Shopify Documentation

eventFactory.checkout_started

The checkout_started event logs an instance of a customer starting the checkout process. This event is available on the checkout page. For Checkout Extensibility, this event is triggered every time a customer enters checkout. For non-checkout extensible shops, this event is only triggered the first time a customer enters checkout.

Source: Shopify Documentation

eventFactory.checkout_address_info_submitted

The checkout_address_info_submitted event logs an instance of a customer submitting their mailing address. This event is only available in checkouts where Checkout Extensibility for customizations is enabled.

Source: Shopify Documentation

eventFactory.checkout_contact_info_submitted

The checkout_contact_info_submitted event logs an instance where a customer submits a checkout form. This event is only available in checkouts where Checkout Extensibility for customizations is enabled.

Source: Shopify Documentation

eventFactory.checkout_shipping_info_submitted

The checkout_shipping_info_submitted event logs an instance where the customer chooses a shipping rate. This event is only available in checkouts where Checkout Extensibility for customizations is enabled.

Source: Shopify Documentation

eventFactory.payment_info_submitted

The payment_info_submitted event logs an instance of a customer submitting their payment information. This event is available on the checkout page.

Source: Shopify Documentation

eventFactory.checkout_completed

The checkout_completed event logs when a visitor completes a purchase. It's triggered once for each checkout, typically on the Thank you page. However, for upsells and post purchases, the checkout_completed event is triggered on the first upsell offer page instead. The event isn't triggered again on the Thank you page. If the page where the event is supposed to be triggered fails to load, then the checkout_completed event isn't triggered at all.

Source: Shopify Documentation