> For the complete documentation index, see [llms.txt](https://docs.chord.co/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.chord.co/collect-data/shopify/browser-events/web-pixel-code.md).

# Web pixel code

This is the Chord web pixel for Shopify. Copy all of it into the **Code** box of your custom pixel, as described in [Step 3: Add the Chord web pixel](/collect-data/shopify/browser-events.md#step-3-add-the-chord-web-pixel).

Paste it exactly as shown, then fill in the empty `const` values at the very top with the values Chord sent you. [Web pixel settings](/collect-data/shopify/browser-events.md#web-pixel-settings) explains each one. The pixel sends nothing until `CDP_WRITE_KEY` and `CDP_DOMAIN` are filled in.

When this page changes, the code below is the latest version. To update a pixel you installed earlier, paste the new code over the old and fill in the same values again.

Built from chord-pixel commit `d1992dd` on 2026-11-09.

```javascript
/**
 * Unified Shopify Custom Web Pixel
 *
 * Combines two delivery paths:
 * 1. postMessage forwarding (all events) — picked up by @chordcommerce/analytics
 *    on Liquid storefronts; harmlessly ignored on Hydrogen/checkout pages.
 * 2. Direct CDP via p.js (checkout events only) — tracks checkout_started through
 *    checkout_completed directly in the sandbox, bypassing the postMessage gap
 *    on Shopify-hosted checkout pages.
 */
// ============================================================================
// Configuration Constants
// ============================================================================
const CDP_WRITE_KEY = ''
const CDP_DOMAIN = ''
const CHORD_OMS_ID = ''
const CHORD_STORE_ID = ''
const CHORD_TENANT_ID = ''
const DEFAULT_LOCALE = 'en-US'
const STORE_DOMAIN = ''
const PIXEL_VERSION = '1.0.0'
let customerPrivacyStatus = init.customerPrivacy
let currency = init.data.cart?.cost?.totalAmount.currencyCode
let locale = DEFAULT_LOCALE
function getStorefrontAnonymousId() {
  return document.cookie
    .split('; ')
    .find((c) => c.startsWith('__eventn_id='))
    ?.split('=')[1]
}
// ============================================================================
// CDP Initialization (lazy — loads p.js on first checkout event)
// ============================================================================
let cdpInitialized = false
function initCdp() {
  if (cdpInitialized) return
  if (!CDP_WRITE_KEY || !CDP_DOMAIN) return
  cdpInitialized = true
  window.chordQ = window.chordQ || []
  window.chord = window.chord || {
    identify: (...args) => window.chordQ?.push((cdp) => cdp?.identify(...args)),
    page: (...args) => window.chordQ?.push((cdp) => cdp?.page(...args)),
    track: (...args) => window.chordQ?.push((cdp) => cdp?.track(...args)),
  }
  const script = document.createElement('script')
  script.async = true
  script.src = `${CDP_DOMAIN}/p.js`
  script.setAttribute('data-init-only', 'true')
  script.setAttribute('data-namespace', 'chord')
  script.setAttribute('data-write-key', CDP_WRITE_KEY)
  document.head.appendChild(script)
  const cdpUserId = window.chord?.user?.()?.userId
  const userId = init.data.customer?.id
  if (userId && userId !== cdpUserId) {
    const traits = getInitCustomerTraits(init)
    cdpIdentify(userId, traits)
  }
}
// ============================================================================
// Consent Management
// ============================================================================
api.customerPrivacy.subscribe('visitorConsentCollected', (event) => {
  customerPrivacyStatus = event.customerPrivacy
})
;(function identifyOnLoad() {
  const customer = init.data.customer
  if (!customer?.id) return
  const traits = {}
  if (customer.email) traits.email = customer.email
  if (customer.firstName) traits.firstName = customer.firstName
  if (customer.lastName) traits.lastName = customer.lastName
  if (customer.phone) traits.phone = customer.phone
  if (customer.ordersCount) traits.ordersCount = customer.ordersCount
  forward('identify', customer.id, traits)
})()
// ============================================================================
// Event Subscriptions — All 13 Shopify events
// ============================================================================
// --- Storefront-only events (postMessage only) ---
analytics.subscribe('page_viewed', (event) => {
  forward('page', {
    path: event.context.document.location.pathname,
    url: event.context.document.location.href,
    title: event.context.document.title,
  })
})
analytics.subscribe('product_viewed', (event) => {
  const { productVariant } = event.data
  forward('track', 'Product Viewed', formatProduct(productVariant))
})
analytics.subscribe('product_added_to_cart', (event) => {
  const { cartLine } = event.data
  if (!cartLine) return
  const product = formatCartLine(cartLine)
  forward('track', 'Product Added', {
    ...product,
    products: [product],
    total: product.value,
  })
})
analytics.subscribe('product_removed_from_cart', (event) => {
  const { cartLine } = event.data
  if (!cartLine) return
  const product = formatCartLine(cartLine)
  forward('track', 'Product Removed', {
    ...product,
    products: [product],
    total: product.value,
  })
})
analytics.subscribe('cart_viewed', (event) => {
  const { cart } = event.data
  if (!cart) return
  forward('track', 'Cart Viewed', {
    cart_id: cart.id,
    currency: cart.cost.totalAmount.currencyCode,
    value: cart.cost.totalAmount.amount,
    products: cart.lines.map(formatCartLine),
  })
})
analytics.subscribe('collection_viewed', (event) => {
  const { collection } = event.data
  forward('track', 'Product List Viewed', {
    list_id: collection.id,
    item_list_id: collection.id,
    list_name: collection.title,
    item_list_name: collection.title,
    products: collection.productVariants.map(formatProduct),
  })
})
analytics.subscribe('search_submitted', (event) => {
  forward('track', 'Products Searched', {
    query: event.data.searchResult.query,
  })
})
// --- Checkout events (postMessage + direct CDP) ---
analytics.subscribe('checkout_started', async (event) => {
  const { checkout } = event.data
  // postMessage (sync, fire-and-forget)
  forward('track', 'Checkout Started', formatCheckout(checkout))
  // Direct CDP
  const coupon = getCoupon(checkout)
  const discount = getDiscount(checkout)
  const props = {
    checkout_type: 'Shopify',
    coupon,
    currency: checkout.currencyCode,
    discount,
    products: checkout.lineItems.map(getLineItem),
    revenue: checkout.subtotalPrice.amount,
    shipping: checkout.shippingLine?.price.amount,
    tax: checkout.totalTax.amount,
    value: checkout.subtotalPrice.amount,
  }
  await cdpTrack('Checkout Started', props)
})
analytics.subscribe('checkout_contact_info_submitted', async (event) => {
  const { checkout } = event.data
  // postMessage (sync, fire-and-forget)
  // Use undefined for userId — email is not a stable ID; the real customer ID
  // is set at checkout_completed when checkout.order.customer.id is available.
  forward('identify', undefined, {
    email: checkout.email,
    phone: checkout.phone,
  })
  forward('track', 'Checkout Step Completed', {
    checkout_id: checkout.token,
    step: 1,
    step_name: 'Contact Info',
  })
  // Direct CDP
  const checkoutTraits = getCheckoutTraits(checkout)
  const initTraits = getInitCustomerTraits(init)
  const traits = {
    ...initTraits,
    ...checkoutTraits,
  }
  const userId = traits.id || undefined
  await cdpIdentify(userId, traits)
  const props = {
    checkout_id: checkout.token,
    step: 1,
  }
  await cdpTrack('Checkout Step Completed', props)
})
analytics.subscribe('checkout_address_info_submitted', async (event) => {
  // postMessage (sync, fire-and-forget)
  forward('track', 'Checkout Step Completed', {
    checkout_id: event.data.checkout.token,
    step: 2,
    step_name: 'Shipping Address',
  })
  // Direct CDP
  const props = {
    checkout_id: event.data.checkout.token,
    step: 2,
  }
  await cdpTrack('Checkout Step Completed', props)
})
analytics.subscribe('checkout_shipping_info_submitted', async (event) => {
  // postMessage (sync, fire-and-forget)
  forward('track', 'Checkout Step Completed', {
    checkout_id: event.data.checkout.token,
    step: 3,
    step_name: 'Shipping Method',
  })
  // Direct CDP
  const props = {
    checkout_id: event.data.checkout.token,
    step: 3,
  }
  await cdpTrack('Checkout Step Completed', props)
})
analytics.subscribe('payment_info_submitted', async (event) => {
  const { checkout } = event.data
  // postMessage (sync, fire-and-forget)
  forward('track', 'Payment Info Entered', {
    checkout_id: checkout.token,
    step: 4,
    currency: checkout.currencyCode,
    value: checkout.subtotalPrice.amount,
    products: checkout.lineItems.map(formatLineItem),
  })
  // Direct CDP
  const coupon = getCoupon(checkout)
  const props = {
    checkout_id: checkout.token,
    coupon,
    currency: checkout.currencyCode,
    products: checkout.lineItems.map(getLineItem),
    step: 4,
    value: checkout.subtotalPrice.amount,
  }
  await cdpTrack('Payment Info Entered', props)
})
analytics.subscribe('checkout_completed', async (event) => {
  const { checkout } = event.data
  // postMessage (sync, fire-and-forget)
  const userId = checkout.order?.customer?.id || undefined
  forward('identify', userId, {
    email: checkout.email,
    phone: checkout.phone,
    firstName: checkout.shippingAddress?.firstName,
    lastName: checkout.shippingAddress?.lastName,
  })
  forward(
    'track',
    'Order Completed',
    formatOrderCompleted(checkout, event.timestamp)
  )
  // Direct CDP
  const coupon = getCoupon(checkout)
  const discount = getDiscount(checkout)
  const orderProps = {
    checkout_id: checkout.token,
    checkout_type: 'Shopify',
    coupon,
    currency: checkout.currencyCode,
    discount,
    email: checkout.email || undefined,
    first_name: checkout.shippingAddress?.firstName || undefined,
    last_name: checkout.shippingAddress?.lastName || undefined,
    order_date: event.timestamp,
    order_id: checkout.order?.id,
    phone: checkout.phone || undefined,
    products: checkout.lineItems.map((item, i) => {
      const price = item.variant?.price.amount
      const itemDiscount = item.discountAllocations.reduce(
        (acc, { amount }) => acc + (amount.amount || 0),
        0
      )
      const revenue = price ? (item.quantity || 1) * price - itemDiscount : 0
      return {
        ...getLineItem(item, i),
        revenue,
      }
    }),
    revenue: checkout.subtotalPrice.amount,
    shipping: checkout.shippingLine?.price.amount,
    shipping_city: checkout.shippingAddress?.city || undefined,
    shipping_country: checkout.shippingAddress?.country || undefined,
    shipping_state: checkout.shippingAddress?.province || undefined,
    shipping_zipcode: checkout.shippingAddress?.zip || undefined,
    subtotal: checkout.subtotalPrice.amount,
    tax: checkout.totalTax.amount,
    total: checkout.totalPrice.amount,
  }
  const checkoutTraits = getCheckoutTraits(checkout)
  const initTraits = getInitCustomerTraits(init)
  const traits = {
    ...initTraits,
    ...checkoutTraits,
  }
  const cdpUserId = traits.id || undefined
  await cdpIdentify(cdpUserId, traits)
  await cdpTrack('Order Completed', orderProps)
})
function forward(type, ...args) {
  try {
    const target =
      typeof window !== 'undefined' && window.top ? window.top : self
    target.postMessage(
      {
        source: 'chord-web-pixel',
        type,
        payload: args,
      },
      '*'
    )
  } catch (err) {
    // Silently fail if postMessage is not available
  }
}
// ============================================================================
// Lightweight Data Formatters (for postMessage forwarding)
// ============================================================================
function formatProduct(variant) {
  const product = variant?.product
  return {
    product_id: product?.id,
    sku: variant?.sku,
    name: product?.title || product?.untranslatedTitle,
    price: variant?.price?.amount,
    currency: variant?.price?.currencyCode,
    category: product?.type,
    brand: product?.vendor,
    variant: variant?.title || variant?.untranslatedTitle,
    variant_id: variant?.id,
    image_url: variant?.image?.src,
    url: product?.url,
  }
}
function formatCartLine(cartLine) {
  const variant = cartLine.merchandise
  const product = variant?.product
  return {
    product_id: product?.id,
    sku: variant?.sku,
    name: product?.title || product?.untranslatedTitle,
    price: variant?.price?.amount,
    currency: cartLine.cost.totalAmount.currencyCode,
    quantity: cartLine.quantity,
    category: product?.type,
    brand: product?.vendor,
    variant: variant?.title || variant?.untranslatedTitle,
    variant_id: variant?.id,
    image_url: variant?.image?.src,
    url: product?.url,
    value: cartLine.cost.totalAmount.amount,
  }
}
function formatLineItem(item, index) {
  const variant = item.variant
  const product = variant?.product
  return {
    product_id: product?.id,
    sku: variant?.sku,
    name: product?.title || product?.untranslatedTitle,
    price: variant?.price?.amount,
    quantity: item.quantity,
    category: product?.type,
    brand: product?.vendor,
    variant: variant?.title || variant?.untranslatedTitle,
    variant_id: variant?.id,
    image_url: variant?.image?.src,
    url: product?.url,
    position: typeof index === 'number' ? index + 1 : undefined,
  }
}
function formatCheckout(checkout) {
  const discount = checkout.lineItems.reduce((acc, item) => {
    return (
      acc +
      item.discountAllocations.reduce(
        (sum, d) => sum + (d.amount.amount || 0),
        0
      )
    )
  }, 0)
  const coupon = checkout.discountApplications.find((d) => {
    if ('amount' in d.value) return d.value.amount > 0
    return d.value.percentage > 0
  })?.title
  return {
    checkout_id: checkout.token,
    currency: checkout.currencyCode,
    value: checkout.subtotalPrice.amount,
    revenue: checkout.subtotalPrice.amount,
    shipping: checkout.shippingLine?.price?.amount,
    tax: checkout.totalTax?.amount,
    discount,
    coupon,
    products: checkout.lineItems.map(formatLineItem),
  }
}
function formatOrderCompleted(checkout, timestamp) {
  const base = formatCheckout(checkout)
  return {
    ...base,
    order_id: checkout.order?.id,
    order_date: timestamp,
    total: checkout.totalPrice.amount,
    subtotal: checkout.subtotalPrice.amount,
    email: checkout.email,
    phone: checkout.phone,
    first_name: checkout.shippingAddress?.firstName,
    last_name: checkout.shippingAddress?.lastName,
    shipping_city: checkout.shippingAddress?.city,
    shipping_state: checkout.shippingAddress?.province,
    shipping_country: checkout.shippingAddress?.country,
    shipping_zipcode: checkout.shippingAddress?.zip,
  }
}
// ============================================================================
// Direct CDP Methods (for checkout events in the sandbox)
// ============================================================================
function cdpEnabled() {
  if (window.chord) return true
  if (!CDP_WRITE_KEY || !CDP_DOMAIN) return false
  initCdp()
  return !!window.chord
}
async function cdpIdentify(userId, traits) {
  if (!cdpEnabled()) return
  const options = await getOptions()
  const anonymousId = getStorefrontAnonymousId()
  await window.chord.identify(userId, traits, {
    ...options,
    ...(anonymousId && {
      anonymousId,
    }),
    context: {
      page: options.page,
    },
  })
}
async function cdpTrack(event, props) {
  if (!cdpEnabled()) return
  const options = await getOptions()
  const meta = await getMeta()
  const anonymousId = getStorefrontAnonymousId()
  await window.chord.track(
    event,
    {
      ...props,
      meta,
      context: {
        page: options.page,
      },
    },
    {
      ...options,
      ...(anonymousId && {
        anonymousId,
      }),
    }
  )
}
// ============================================================================
// CDP Options & Metadata
// ============================================================================
async function getCategoryPreferences() {
  return {
    analytics: customerPrivacyStatus?.analyticsProcessingAllowed,
    marketing: customerPrivacyStatus?.marketingAllowed,
    preferences: customerPrivacyStatus?.preferencesProcessingAllowed,
    sale_of_data: customerPrivacyStatus?.saleOfDataAllowed,
  }
}
async function getOptions() {
  const categoryPreferences = (await getCategoryPreferences()) || {}
  return {
    consent: {
      categoryPreferences,
    },
    page: {
      path: init.context.document.location.pathname,
      url: init.context.document.location.href,
    },
  }
}
async function getMeta() {
  if (!currency) {
    currency = await browser.cookie.get('cart_currency')
  }
  return {
    i18n: {
      currency,
      locale,
    },
    ownership: {
      oms_id: CHORD_OMS_ID,
      store_id: CHORD_STORE_ID,
      tenant_id: CHORD_TENANT_ID,
    },
    platform: {
      name: 'Shopify',
      type: 'web',
    },
    store: {
      domain: STORE_DOMAIN,
    },
    version: {
      major: 3,
      minor: 0,
      patch: 0,
    },
  }
}
// ============================================================================
// Rich Data Formatters (for direct CDP — adapted from legacy pixel)
// ============================================================================
function getLineItem(item, position) {
  const variant = 'variant' in item ? item.variant : item.merchandise
  const product = variant?.product
  const discountAllocations =
    'discountAllocations' in item ? item.discountAllocations : undefined
  const coupon = discountAllocations?.find((al) => al?.amount?.amount > 0)
    ?.discountApplication.title
  return {
    brand: product?.vendor,
    category: product?.type || undefined,
    coupon,
    image_url: variant?.image?.src,
    line_item_id: 'id' in item ? item.id : undefined,
    name: product?.title || product?.untranslatedTitle,
    position: typeof position === 'number' ? position + 1 : undefined,
    price: variant?.price.amount,
    product_id: product?.id,
    quantity: ('quantity' in item && item.quantity) || undefined,
    sku: variant?.sku || undefined,
    url: product?.url,
    variant: variant?.title || variant?.untranslatedTitle || undefined,
    variant_id: variant?.id,
  }
}
function getDiscount(checkout) {
  return checkout.lineItems.reduce((acc, item) => {
    const { discountAllocations } = item
    const discount = discountAllocations.reduce((innerAcc, { amount }) => {
      return innerAcc + (amount.amount || 0)
    }, 0)
    return acc + discount
  }, 0)
}
function getCoupon(checkout) {
  return checkout.discountApplications.find(({ value }) => {
    if ('amount' in value) return value.amount > 0
    return value.percentage > 0
  })?.title
}
function getCheckoutTraits(checkout) {
  const traits = {}
  if (checkout.email) traits.email = checkout.email
  if (checkout.phone) traits.phone = checkout.phone
  const customerId = checkout.order?.customer?.id
  if (customerId) traits.id = customerId
  return traits
}
function getInitCustomerTraits(init) {
  const { customer } = init.data
  const traits = {}
  if (customer?.email) traits.email = customer.email
  if (customer?.firstName) traits.firstName = customer.firstName
  if (customer?.id) traits.id = customer.id
  if (customer?.lastName) traits.lastName = customer.lastName
  if (customer?.ordersCount) traits.ordersCount = customer.ordersCount
  if (customer?.phone) traits.phone = customer.phone
  return traits
}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.chord.co/collect-data/shopify/browser-events/web-pixel-code.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
