> 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/everyone-else/browser-events.md).

# Browser events

Browser events track what shoppers do on your storefront: page views, product views, add to cart, checkout steps and orders. You send them with `@chordcommerce/analytics`, Chord's browser SDK. It delivers events to Chord, runs your browser (device-mode) destinations such as Meta Pixel or GA4, and handles identity and consent.

These steps work for any platform, including Salesforce Commerce Cloud, Adobe Commerce (Magento), SAP Commerce and custom builds. Shopify headless storefronts, including Hydrogen, also use them to install the package.

## Before you start

Ask Chord for these values:

* Your CDP domain and write key
* Your OMS ID, store ID and tenant ID

## Step 1: Add the analytics package

Choose how to load the package:

* **npm**, if your storefront is a JavaScript app built with a bundler, such as React, Next.js or Vue.
* **Script tag**, if your storefront renders pages on the server from templates, as Salesforce Commerce Cloud and Adobe Commerce (Magento) usually do.

### npm

Install the package:

```shell
npm install @chordcommerce/analytics@1.24.5
# or
yarn add @chordcommerce/analytics@1.24.5
```

### Initialize the library

`@chordcommerce/analytics` is a browser-side library. It needs access to `window` and the DOM to load destination scripts, listen for events and send tracking data. **It must not be initialized during server-side rendering (SSR).**

If your framework renders pages on the server (Next.js, Remix, Nuxt, Hydrogen and so on), make sure Chord only initializes in the browser. Use one of these approaches.

#### Option A: React `useEffect` (recommended for React frameworks)

`useEffect` only runs in the browser, never during SSR. This is the simplest approach for React-based frameworks like Next.js and Hydrogen.

```tsx
'use client'

import { ChordAnalytics } from '@chordcommerce/analytics'
import { createContext, useContext, useEffect, useState } from 'react'

const ChordContext = createContext<ChordAnalytics | null>(null)

export function ChordProvider({ children }: { children: React.ReactNode }) {
  const [chord, setChord] = useState<ChordAnalytics | null>(null)

  useEffect(() => {
    setChord(
      new ChordAnalytics({
        cdpDomain: process.env.NEXT_PUBLIC_CHORD_DOMAIN,
        cdpWriteKey: process.env.NEXT_PUBLIC_CHORD_WRITE_KEY,
        consent: 'onetrust', // your consent platform; remove if you don't use one. See Consent
        debug: false,
        enableLogging: false,
        formatters: {
          objects: {
            cart: (props) => props.cart,
            checkout: (props) => props.checkout,
            lineItem: (props) => props.lineItem,
            product: (props) => props.product,
          },
        },
        metadata: {
          i18n: { currency: 'USD', locale: 'en-US' },
          ownership: {
            omsId: process.env.NEXT_PUBLIC_CHORD_OMS_ID!,
            storeId: process.env.NEXT_PUBLIC_CHORD_STORE_ID!,
            tenantId: process.env.NEXT_PUBLIC_CHORD_TENANT_ID!,
          },
          platform: { name: 'your-platform', type: 'web' },
          store: { domain: 'your-store-domain' },
        },
      }),
    )
  }, [])

  return (
    <ChordContext.Provider value={chord}>
      {children}
    </ChordContext.Provider>
  )
}

export function useChord() {
  return useContext(ChordContext)
}
```

Then wrap your app in `<ChordProvider>` and use the `useChord()` hook in any component that sends tracking events. It returns `null` until the library has loaded in the browser, so call it as `chord?.trackProductViewed(...)`.

#### Option B: Next.js dynamic import with `ssr: false`

This keeps the Chord module from being imported on the server at all. It's useful when you want all Chord logic in a single component.

```tsx
import dynamic from 'next/dynamic'

const ChordProvider = dynamic(
  () => import('./ChordProvider').then((m) => m.ChordProvider),
  { ssr: false },
)

export default function Layout({ children }) {
  return (
    <ChordProvider>
      {children}
    </ChordProvider>
  )
}
```

#### Option C: `typeof window` guard

The simplest approach for non-React frameworks or plain JavaScript. Check for the browser before initializing.

```typescript
import { ChordAnalytics } from '@chordcommerce/analytics'

if (typeof window !== 'undefined') {
  window.chord = new ChordAnalytics({
    cdpDomain: 'https://production.cdp.ingest.chord.co',
    cdpWriteKey: 'your-chord-write-key',
    consent: 'onetrust', // your consent platform; remove if you don't use one. See Consent
    debug: false,
    enableLogging: false,
    formatters: {
      objects: {
        cart: (props) => props.cart,
        checkout: (props) => props.checkout,
        lineItem: (props) => props.lineItem,
        product: (props) => props.product,
      },
    },
    metadata: {
      i18n: { currency: 'USD', locale: 'en-US' },
      ownership: {
        omsId: 'your-oms-id',
        storeId: 'your-store-id',
        tenantId: 'your-tenant-id',
      },
      platform: { name: 'your-platform', type: 'web' },
      store: { domain: 'your-store-domain' },
    },
  })
}
```

{% hint style="info" %}
The formatters above pass your data through unchanged. Replace them with functions that map your platform's data to the fields the Chord [tracking plan](/collect-data/tracking-plan.md) expects. See [Formatters](/collect-data/everyone-else/sdk-reference.md#formatters).
{% endhint %}

### Script tag

Load the package from the jsDelivr CDN in your site's main layout template, so it runs on every page. Put it just before the closing `</body>` tag, and replace the `your-...` values with the ones Chord gave you:

```html
<script type="module">
  import { ChordAnalytics } from 'https://cdn.jsdelivr.net/npm/@chordcommerce/analytics@1.24.5/+esm'

  window.chord = new ChordAnalytics({
    cdpDomain: 'https://production.cdp.ingest.chord.co',
    cdpWriteKey: 'your-chord-write-key',
    consent: 'onetrust', // your consent platform; remove if you don't use one. See Consent
    debug: false,
    enableLogging: false,
    formatters: {
      objects: {
        cart: (props) => props.cart,
        checkout: (props) => props.checkout,
        lineItem: (props) => props.lineItem,
        product: (props) => props.product,
      },
    },
    metadata: {
      i18n: { currency: 'USD', locale: 'en-US' },
      ownership: {
        omsId: 'your-oms-id',
        storeId: 'your-store-id',
        tenantId: 'your-tenant-id',
      },
      platform: { name: 'your-platform', type: 'web' },
      store: { domain: 'your-store-domain' },
    },
  })
</script>
```

Fill `currency` and `locale` from your template engine, so they match the shopper's session. `window.chord` is then available to every script on the page.

{% hint style="info" %}
Keep the version in the URL pinned to an exact release, as above. An unpinned URL picks up new releases without warning.
{% endhint %}

## Step 2: Add tracking calls

Once the library is initialized, use the SDK methods to track commerce events:

```typescript
// Page views
chord.page()

// Product interactions
chord.trackProductViewed({ cart, product: { product, quantity: 1 } })
chord.trackProductAdded({ cart, product: { product, quantity: 1 } })
chord.trackProductRemoved({ cart, lineitem })

// Cart
chord.trackCartViewed({ cart })

// Checkout
chord.trackCheckoutStarted({ checkout })
chord.trackPaymentInfoEntered({ checkoutId, step: 1, paymentMethod: 'credit_card' })

// Orders
chord.trackOrderCompleted({
  orderId: 'ORD-123',
  orderDate: '2026-03-09',
  currency: 'USD',
  revenue: 89.99,
  total: 99.99,
  products: [{ /* ... */ }],
})

// User identity
chord.identify(userId, { email: 'user@example.com' })
```

For every method and its parameters, see the [SDK reference](/collect-data/everyone-else/sdk-reference.md#methods). Events not in the tracking plan are [custom events](/collect-data/everyone-else/sdk-reference/custom-events.md).

## Step 3: Save the anonymous ID on the cart

Your [server events](/collect-data/everyone-else/server-events.md) need a way to link each order to the shopper's browsing. Before the shopper reaches checkout, read the anonymous ID the package assigned to the shopper from the `__eventn_id` cookie, and save it on the cart or order in your platform. Your backend then sends it with each server event. See [Server events](/collect-data/everyone-else/server-events.md#step-3-link-events-to-the-browser-session).

## Verify the installation

1. Set `debug: true` and `enableLogging: true` in your configuration while you test. Together they check events against the tracking plan and log warnings for missing or incorrect properties to the browser console.
2. Browse your storefront and check the browser console for Chord event logging.
3. Check that events appear in **Live Events** in Chord.
4. Turn `debug` and `enableLogging` off again when you're done.

## Consent

If you use a consent management platform, such as OneTrust, Cookiebot or Ketch, set the `consent` option so Chord respects each shopper's choices. See [Consent](/collect-data/everyone-else/browser-events/consent.md).


---

# 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/everyone-else/browser-events.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.
