# Track Storefront Next Activity with Analytics

Monitor your storefront activity by collecting analytics data. Storefront Next provides a built-in solution for analytics, which is integrated with Einstein, Active Data, and Data 360 (formerly Data Cloud). Implement analytics by using the analytics hook and events. Also, you can create integrations with other analytics systems.

## How It Works

Storefront Next pages invoke the analytics hook to trigger the creation of `AnalyticsEvent` objects.

For example, on the product details page, you see this script.

```tsx
const analytics = useAnalytics();

...
useEffect(() => {
	...
  void analytics.trackViewProduct({
    product: productData,
  });
	...
}
```

The `trackViewProduct` method creates a ViewProduct AnalyticsEvent that's passed into an internal `track` function. This `track` function sends the AnalyticsEvent to registered EngagementAdapters that handle the process of sending the event through to a target analytics system.

## Tracking Consent

No analytics events are sent until a shopper grants consent, which helps you avoid tracking that violates privacy regulations such as the General Data Protection Regulation (GDPR).

Storefront Next supports a two-layer consent model:

- **Binary consent (default):** A consent banner asks the shopper to accept or decline all tracking. This choice is persisted through the SLAS token (stored in the `dw_dnt` cookie). This is the default behavior.
- **Per-adapter consent categories:** Each engagement adapter can declare the consent category it requires (for example, `analytics` or `marketing`). An event reaches an adapter only if the shopper has granted that category, which supports GDPR-style, category-level consent.

When a shopper accepts the consent banner, Storefront Next builds a set of consent preferences (the granted categories) and forwards them to every adapter. Each adapter checks those preferences before sending an event. Until the shopper accepts, the consent preferences are undefined and no events are sent.

### Configure Consent Categories

Enable tracking consent and declare the categories your storefront supports in `config.server.ts`. The `consentCategories` array defines which categories are granted when a shopper accepts the consent banner.

```ts
analytics: {
    trackingConsent: {
        enabled: true,
        defaultTrackingConsent: TrackingConsent.Declined,
        consentCategories: ['necessary', 'analytics', 'marketing', 'personalization'],
        position: 'bottom-right',
    },
},
```

The default configuration defines four categories that align with common consent-management conventions:

- `necessary`: Essential cookies and tracking required for site functionality
- `analytics`: Usage analytics and performance measurement
- `marketing`: Advertising, retargeting, and campaign tracking
- `personalization`: Product recommendations and personalized experiences

Category values are arbitrary strings, so you can define whatever categories match your consent-management platform (for example, OneTrust, Cookiebot, or Usercentrics). There's no hard-coded set.

:::note
If `trackingConsent.enabled` is `true` but `consentCategories` isn't configured, the consent banner has no categories to grant when a shopper accepts, so adapters that require a category never fire. Configure `consentCategories` to specify the categories your storefront grants on acceptance.
:::

### Require a Consent Category per Adapter

To send events to an adapter only when the shopper has granted a specific category, set the `consentCategory` field on that adapter in `config.server.ts`.

```ts
engagement: {
    adapters: {
        einstein: {
            enabled: true,
            consentCategory: 'personalization',
            // ... other config
        },
        activeData: {
            enabled: true,
            consentCategory: 'analytics',
            // ... other config
        },
    },
}
```

This table shows when an adapter that declares a `consentCategory` fires.

| Shopper Consent              | Granted Categories                                           | Adapter Fires?                                          |
| ---------------------------- | ------------------------------------------------------------ | ------------------------------------------------------- |
| Accepts all                  | `['necessary', 'analytics', 'marketing', 'personalization']` | Yes                                                     |
| Accepts only some categories | for example, `['necessary']`                                 | Only when the adapter's category is in the granted list |
| Declines                     | `[]`                                                         | No                                                      |
| Not yet decided              | `undefined`                                                  | No                                                      |

If you omit `consentCategory`, the adapter doesn't apply its own category check and fires for every event that reaches the adapter. No events flow until the shopper accepts the consent banner, so omitting `consentCategory` doesn't bypass consent. This is useful for adapters that manage consent internally, such as a Google Tag Manager adapter that uses Google Consent Mode.

By default, all built-in adapters—Einstein, Data 360, and Active Data—set `consentCategory: 'analytics'`, so each fires only after the shopper grants the `analytics` category. With the default binary consent banner, granting consent maps to all configured categories, so `'analytics'` effectively means the shopper accepted tracking. The gate matters once you offer granular, per-category consent: an adapter set to `'analytics'` then stays silent for a shopper who accepted, say, only `'marketing'`.

## Set Up Einstein Activities

To receive Einstein Activities events, set up Einstein Activities.

1. Set up Einstein by using the Einstein Configurator. See [Einstein Activities](https://developer.salesforce.com/docs/commerce/einstein-api/guide/einstein-activities-overview.md) in the *B2C Commerce Einstein API Guide*.
2. In `config.server.ts`, update the Einstein configuration. In the `einsteinId` field, specify the ID of the API client you created in the Einstein Configurator.

   ```
               engagement: {
                adapters: {
                    einstein: {
                        enabled: true,
                        host: 'https://api.cquotient.com',
                        einsteinId: '<API Client ID from Einstein Configurator>',
   ```

## Set Up Active Data

To receive Active Data events, set up Active Data in Business Manager. Active Data in Storefront Next supports product views on the Product Detail Page (PDP) and product impressions on a Product List Page (PLP) or recommendation.

### Prerequisites

- Configure Active Data in Business Manager by following the steps in the next sections.

### Step 1: Enable Data Collection

Open a support case asking Salesforce Customer Support to enable data collection.

### Step 2: Confirm That Sites Are Online

1. Log into Business Manager.
2. Click App Launcher ![App Launcher](https://a.sfdcstatic.com/developer-website/sfdocs/commerce-cloud/media/sfnext/shared/appLauncher.png) and then select **Administration > Sites > Manage Sites**.
3. Confirm that the status is **Online** for the sites for which you want to track metrics. Active Data is collected only for live (online) sites.

### Step 3: Confirm that Tracking (Default for New Storefront Session) Is Enabled

1. Log into Business Manager.
2. Click App Launcher ![App Launcher](https://a.sfdcstatic.com/developer-website/sfdocs/commerce-cloud/media/sfnext/shared/appLauncher.png) and then select **Merchant Tools > Site Preferences > Privacy**.
3. Confirm that Tracking (Default for New Storefront Session) is set to **Enabled**.
   Active data events are sent from Storefront Next to the `__Analytics-Start` controller in B2C Commerce. One way you can confirm that active data tracking is working is by viewing network requests and checking if the request to `__Analytics-Start` is responding with an `HTTP 200` code.

### Step 4: Configure Active Data

1. In `config.server.ts`, enter the Active Data host. In the `host` field, specify the URL of the B2C Commerce instance.

   ```
                       activeData: {
                           enabled: true,
                           host: 'https://{instance}.dx.commercecloud.salesforce.com',
                           siteUUID: '8bb1ea1b04ac3454d36b83a888',
                           eventToggles: {
                               view_page: true,
                               view_product: true,
   ```

2. If you have a hybrid storefront, specify the site UUID in the `siteUUID` field. In SFRA, get the site UUID from the value that comes after `dwac_` in the cookie in SFRA. This cookie appears only when a user is logged into SFRA.

   :::note
   If `siteUUID` isn't set for the hybrid storefront, all Active Data events are treated as having come from anonymous guest users. In pure Storefront Next and PWA Kit Composable implementations, there is no `dwac_ cookie`, so all events are treated as coming from anonymous users.
   :::

## Set Up Data 360

The Data 360 adapter streams page, search, product-view, and impression interactions to a Data 360 Website connector. It maps `view_page`, `view_product`, `view_search`, `view_category`, and `view_recommender`. Search, category, and product events send a separate Page View interaction when shared Page View tracking blocks their path and `view_page` is enabled. A direct search sends one Search interaction per search execution and correlates its product impressions across pagination. Recommendations don't derive a Page View. Cart, checkout, wishlist, and click events have no Data 360 mapping and ship disabled.

For how to provision Data 360, connect your storefront, and get the tenant and app source IDs, see [Send Storefront Next Analytics Events to Data 360](/docs/commerce/b2c-commerce/guide/sfnext-data360.md).

The adapter is configured in `config.server.ts` under `engagement.adapters.data360`.

```ts
engagement: {
    adapters: {
        data360: {
            enabled: true,
            appSourceId: '<your-app-source-id>',
            tenantId: '<your-tenant-id>',
            siteId: '<your-site-id>',
            webStoreId: 'sfnext',
            consentCategory: 'analytics',
            eventToggles: {
                view_page: true,
                view_product: true,
                view_search: true,
                view_category: true,
                view_recommender: true,
                // Cart, checkout, wishlist, and click events have no Data 360
                // mapping and ship disabled.
            },
        },
    },
}
```

- `<your-app-source-id>` and `<your-tenant-id>` come from the provisioned Website connector.
- `<your-site-id>` is the B2C Commerce site ID, sent as `siteId`/`internalOrganizationId` on every interaction.
- `webStoreId` The default value is `sfnext`. It separates Storefront Next traffic from PWA Kit's (`pwa`) in a shared data lake object (DLO).
- `consentCategory` defaults to `analytics`. Because Data 360 does identity resolution and marketing activation, merchants using granular consent can gate it on `marketing` or `personalization` instead.

### Multisite Event Context

Engagement adapters are initialized with static configuration, but the current site and locale are sent at event time through `EventSiteInfo`. The analytics hook constructs `{ siteId: site.id, localeId: language }` from the active site context and passes it to every registered adapter. Active Data uses both values to build its endpoint URL, and Data 360 prefers the event-time site ID for `siteId` and `internalOrganizationId`. Einstein continues to use the site ID configured at initialization.

Engagement adapter configuration is protected. To change adapter settings, update `config.server.ts`, rebuild, and redeploy.

## Analytics Events

Storefront Next provides these built-in event types.

- ViewPageEvent
- ViewProductEvent
- ViewSearchEvent
- ViewCategoryEvent
- ViewRecommenderEvent
- ClickProductInCategoryEvent
- ClickProductInSearchEvent
- ClickProductInRecommenderEvent
- CartItemAddEvent
- CheckoutStartEvent
- CheckoutStepEvent

Each event type defines different fields corresponding to the data required by that specific event.

All types extend `BaseEvent`, which provides the customer fields that are shared across all events, such as `usid` or `customerId`. All events also define a string `eventType` field that EngagementAdapters use during event handling. All types are included as part of the union type `AnalyticsEvent`.

For example, a `ViewProductEvent` Analytics event triggers both an Einstein ViewEvent and an Active Data product view with the default setup. The `ViewSearchEvent` and `ViewCategoryEvent` Analytics events similarly send the ViewSearch and ViewCategory Einstein events alongside Active Data product impressions.

### Adding More Event Types

Storefront Next uses typescript module augmentation to extend existing events and add new event types. To add new event types, use the `AnalyticsEventExtensions` interface. Types added to `AnalyticsEventExtensions` are included in the `AnalyticsEvent` union type.

For example, let's add a new `CartItemRemove` event that triggers when a shopper removes items from the basket.

1. In a TypeScript declaration file, define our new event type. The new event must extend BaseEvent and define the `eventType` field. We also include a `cartItems` field for the items we're removing from cart.

```ts
declare module "@salesforce/storefront-next-runtime/events" {
  interface AnalyticsEventExtensions {
    CartItemRemove: BaseEvent & {
      eventType: "cart_item_remove";
      cartItems: Array<BasketProductItem>;
    };
  }
}
```

2. Update the analytics hook to support the new event.

```ts
/**
 * Track removing items from cart
 */
const trackCartItemRemove = async (data: {
  cartItems: ShopperBasketsV2.schemas["ProductItem"][];
}) => {
  return trackEvent(authPromiseRef.current, appConfig, trackingConsent, "cart_item_remove", {
    cartItems: data.cartItems,
  });
};
```

The `trackEvent` function creates the `CartItemRemove` event from the event type, handles Do Not Track, and adds session information such as the `usid` to the event.

3. Update engagement adapters to handle the new event type.

The `track` function sends `CartItemRemove` events to all registered engagement adapters. Therefore, the adapters need to know if they must handle or ignore `CartItemRemove` events.

4. Configure Engagement adapters to handle or ignore an event in `config.server.ts`. To have the Einstein adapter process `CartItemRemove` while having the Active Data adapter ignore the event, make the following changes inside `config.server.ts`.

```ts
engagement: {
    adapters: {
        einstein: {
            // ... other config
            eventToggles: {
                // ... existing events
                cart_item_remove: true,  // ← Enable for Einstein
            },
        },
        activeData: {
            // ... other config
            eventToggles: {
                // ... existing events
                cart_item_remove: false,  // ← Disable for Active Data
            },
        },
    },
}
```

5. Next, update the existing adapters that process `CartItemRemove`. Each adapter handles the event differently. For example, if we want to send `CartItemRemove` to a non-existent Einstein activity called `removeFromCart`, make this change.

```ts
// Maps event types to Einstein Activity API endpoints
const einsteinEventToEndpointMap: Record<AnalyticsEvent['eventType'], string> = {
    // ... other event mappings
    cart_item_remove: 'removeFromCart'
};

// Converts events into a payload that aligns with Einstein Activity API
function convertEventToEinsteinActivity(event: AnalyticsEvent, realm: string, isProduction: boolean): EinsteinActivity {
  // ... other events
  case 'cart_item_remove':
      return {
          ...baseActivity,
          products: event.cartItems.map((item: ShopperBasketsV2.schemas['ProductItem']) =>
              extractEinsteinItemInfoFromCartItem(item)
          ),
      };
```

:::note
Einstein doesn't have a `removeFromCart` endpoint. If this event is sent from your browser, you see Einstein return an HTTP 400 response.
:::

6. Update the pages that fire the new event.

Now, our `CartItemRemove` event is ready to be used. The next steps are to update the components that handle cart item removal to invoke the analytics hook and test.

```ts
void analytics.trackCartItemRemove({
  cartItems: [removedItem],
});
```

## Engagement Adapters

Engagement Adapters define how an event are sent to an analytics system. Storefront Next includes adapters for Einstein Activities, Active Data, and Data 360 (a Salesforce Data 360 adapter that streams view and impression interactions to a Data 360 Website connector).

Engagement adapters follow the `EngagementAdapter` interface:

```ts
export interface EngagementAdapter extends EventAdapter {
  name: string;
  sendEvent?: (
    event: AnalyticsEvent,
    siteInfo?: EventSiteInfo,
    consentPreferences?: ConsentPreferences,
  ) => Promise<unknown>;
  send?: (url: string, options?: RequestInit) => Promise<Response>;
}
```

The key method used by the analytics system is the `sendEvent` function. The internal `track` function invokes this method when an event is received. The `sendEvent` function takes an `AnalyticsEvent` and converts that event into a payload that can be sent to the target analytics system.

As a best practice, Storefront Next uses the [Beacon API](https://developer.mozilla.org/en-US/docs/Web/API/Beacon_API) (`navigator.sendBeacon`) to send analytics events to target systems because it's non-blocking.

:::note
Because Storefront Next sends analytics events with the Beacon API instead of `fetch` or `XMLHttpRequest`, these requests don't appear under the **Fetch/XHR** filter in your browser's Network tab, and tools that inspect only fetch requests, such as the Einstein Chrome extension, don't capture them. To verify that events are being sent, look for requests to the analytics endpoints (for example, `api.cquotient.com` for Einstein, or the `__Analytics-Start` controller for Active Data) in the Network tab's full request list or under its **Other** filter.
:::

### Adding More Adapters

Keep the following in mind when implementing your own EngagementAdapter.

- Use `eventToggles` to determine which events the adapter processes. This helps fine-tune the adapter to only handle the events the target analytics system supports.

- `sendEvent` has two main tasks. Consider implementing these tasks as separate functions that `sendEvent` invokes. These tasks are:

<!---->

- Converting an AnalyticsEvent object into the appropriate payload for the target analytics system
- Sending the payload to the target system

<!---->

- Register the EngagementAdapter so that it receives AnalyticsEvents from the internal `track` function. To register an EngagementAdapter, modify the `src/adapters.index.ts` file and invoke the `addAdapter` method.

  ```ts
  export function initializeEngagementAdapters(appConfig: AppConfig) {
    // ... other adapters

    addAdapter(
      'custom-adapter', {
        name: 'custom-adapter'
          sendEvent: async (event: AnalyticsEvent) => Promise.resolve({}),
      }
    )
  ```

As a best practice, adapters provide a function for creating the adapter so you're more likely to see this script.

```ts
import {createCustomAdapter} from './customAdapter'

export function initializeEngagementAdapters(appConfig: AppConfig) {

  // ... other adapters

  addAdapter(
    'custom-adapter',
    createCustomAdapter()
  )
```

## See Also

[Einstein Activities](https://developer.salesforce.com/docs/commerce/einstein-api/guide/einstein-activities-overview.md)
[Active Merchandising](https://help.salesforce.com/s/articleView?id=cc.b2c_active_merchandising.htm)
