Docs

Shopify

Shopify needs two pieces: the snippet in the theme (storefront pages) and a custom pixel for checkout and thank-you pages, which themes cannot reach.

Install

*Online Store → Themes → … → Edit code → layout/theme.liquid*, inside <head>:

layout/theme.liquid
<script>window.x3=window.x3||function(){(window.x3q=window.x3q||[]).push(arguments)}</script>
<script async src="{{API_ORIGIN}}/v1/s/x3.full.js?k={{PUBLISHABLE_KEY}}" data-key="{{PUBLISHABLE_KEY}}"></script>

Settings → Customer events → Add custom pixel, name it "3x", permission Analytics, and paste:

shopify/customer-events/3x-pixel.js
// 3x custom pixel – runs in Shopify's pixel sandbox (no DOM of the shop page, no storage).
const KEY = "{{PUBLISHABLE_KEY}}";
const ENDPOINT = "{{API_ORIGIN}}/v1/e";
const sessionId = crypto.randomUUID();

// One batch (wire format v2) per checkout event: a pageview of a fixed checkout path plus the event.
function send(path, name, props) {
  const view = init.context.window;
  const pageview = {
    id: crypto.randomUUID(), seq: 1, age: 0, nav: "load",
    host: init.context.document.location.hostname, path, variant: null, lang: null, ref: null,
    attrFrom: "none", attr: {},
    vw: view.innerWidth, vh: view.innerHeight, sw: view.innerWidth, tp: false,
    ph: 0, sy: 0, sp: 0, marks: {}, sec: {}, eng: 0, clk: 0, iclk: 0, vids: [],
  };
  const batch = {
    v: 2, k: KEY, sid: sessionId, fresh: true, consent: false, test: false,
    pvs: [pageview], ev: [{ pv: pageview.id, n: 0, t: 0, e: name, p: props }], lib: "x3-shopify-pixel@1",
  };
  fetch(ENDPOINT, {
    method: "POST", keepalive: true, credentials: "omit",
    headers: { "content-type": "text/plain;charset=UTF-8" },
    body: JSON.stringify(batch),
  }).catch(() => {});
}

analytics.subscribe("checkout_started", (event) => {
  send("/checkout", "checkout_started", { items: event.data.checkout.lineItems.length });
});
analytics.subscribe("checkout_completed", (event) => {
  const checkout = event.data.checkout;
  send("/checkout/thank-you", "checkout_completed", { value: Number(checkout.totalPrice.amount), currency: checkout.currencyCode });
});

Each checkout event is recorded as a pageview of a fixed path (/checkout, /checkout/thank-you), never of the tokenized checkout URL. The server links it to the visitor's storefront session through the daily visitor hash. App pixels (strict sandbox) use the DOM-free transport of @3x-engine/sdk/pixel, which builds the same batches.

Verify

  1. Visit the storefront in a private window; POST {{API_ORIGIN}}/v1/e → 204.
  2. Customer events → 3x → Test opens the pixel helper; place a test order with a bogus gateway.
  3. The dashboard shows checkout_started / checkout_completed in the event catalog.

Add your shop domains (YOUR-STORE.myshopify.com and the custom domain) to the project's domains and allowed origins – the pixel reports the shop's host.

Proxy (optional)

Shopify themes cannot host server routes; use an app proxy of a custom app or a Cloudflare Worker on your custom domain, then point the snippet at it:

layout/theme.liquid
<script async src="{{PROXY_PATH}}/v1/s/x3.full.js?k={{PUBLISHABLE_KEY}}" data-key="{{PUBLISHABLE_KEY}}" data-host="{{PROXY_PATH}}"></script>

Shopify's Customer Privacy API decides for pixels automatically (pixels with Analytics permission run only when allowed and replay events after consent). For the storefront snippet, forward the decision:

layout/theme.liquid
<script>
  document.addEventListener("visitorConsentCollected", function (event) {
    x3("consent", event.detail.analyticsAllowed ? "granted" : "denied");
  });
</script>

Events

sections/main-product.liquid
<button type="submit" name="add" data-x3-event="add_to_cart" data-x3-prop-product="{{ product.handle }}">Add to cart</button>

Register checkout_completed with a value prop and use it as a goal with a value to get revenue per channel and ROAS on the ads report.