---
title: Storefront Hooks Documentation
slug: experience/storefront-hooks-documentation
docTags: 
createdAt: 2025-12-09T15:55:26.600Z
---

Storefront hooks provide a communication interface between the Purple Experience and custom JavaScript code (`custom.js`). This system allows external code to extend and customize Experience's behavior without modifying the core application.

## Setup

Hooks are defined in the `custom.js` file and registered on the global `window.storefrontHooks` object. The hooks are triggered at specific points during the application lifecycle.

```javascript
// Example setup in custom.js
window.storefrontHooks = {
  onPurpleServiceInit: () => {
    console.log('Purple service initialized');
  },
  // ... other hooks
};
```

## Available Hooks

### Service Lifecycle Hooks

### `onPurpleServiceInit()`

- **Triggered**: When the PurpleService is fully initialized and ready to serve requests
- **Use case**: Set up initial configurations
- **Parameters**: None
- **Return**: void

```javascript
window.storefrontHooks.onPurpleServiceInit = () => {
  // Initialize tracking services, ads, or other external integrations
};
```

### Navigation Hooks

### `onNavigationEnd(params)`

- **Triggered**: After Angular's NavigationEnd event. Despite its name, it is actually triggered at the **beginning** of the navigation process, right when the page starts rendering.
- **Use case**: Track page views, update analytics
- **Parameters**:
  - `event`: Angular NavigationEnd event object
  - `router`: Angular Router instance
  - `viewContext`: Context data object
- **Return**: void

```javascript
window.storefrontHooks.onNavigationEnd = (params) => {
  console.log('Navigated to:', params.event.url);
  // Track page views
};
```

### `onNavigationStable(params)`

- **Triggered**: When the app becomes stable after Angular navigation (including initial load)
- **Use case**: Refresh ads after route changes, perform post-navigation actions
- **Parameters**:
  - All parameters from `onNavigationEnd`
  - `previousUrl`: Previous URL (optional)
  - `currentUrl`: Current URL
- **Return**: void

```javascript
window.storefrontHooks.onNavigationStable = (params) => {
  // Ideal for refreshing ads after route changes
};
```

### Content Hooks

### `onContentLoaded(params)`

- **Triggered**: After a content body component successfully loads the content along with its resources
- **Use case**: Content-specific tracking
- **Parameters**:
  - `content`: CatalogContent object
  - `post`: CatalogPost object (for bundle content)
  - `context`: Context data object
- **Return**: void

```javascript
window.storefrontHooks.onContentLoaded = (params) => {
  console.log('Content loaded:', params.content.name);
  // Track content views
};
```

### Purchase Hooks

### `beforeContentPurchase(purchaseInfo)`

- **Triggered**: Before a content purchase is performed,  when processPurchaseAction is performed
- **Use case**: Validate purchases, show custom purchase flows, intercept purchases
- **Parameters**:
  - `purchaseInfo.actionConfig`: Purchase action configuration
- **Return**: `HookBeforePurchaseResult` (continue | cancel | error)

```javascript
window.storefrontHooks.beforeContentPurchase = async (purchaseInfo) => {
  // Custom validation or purchase flow
  if (customValidation()) {
    return { type: 'continue' };
  } else {
    return { type: 'cancel' };
  }
};
```

### `beforeSubscriptionPurchase(purchaseInfo)`

- **Triggered**: Before a subscription purchase is performed, when processSubscribeAction is performed
- **Use case**: Custom subscription flows, validation
- **Parameters**:
  - `purchaseInfo.actionConfig`: Subscription action configuration
- **Return**: `HookBeforePurchaseResult` (continue | cancel | error)

```javascript
window.storefrontHooks.beforeSubscriptionPurchase = async (purchaseInfo) => {
  // Custom subscription logic
  return { type: 'continue' };
};
```

### User State Hooks

### `entitlementChanged(params)`

- **Triggered**: When user entitlement changes (login/logout/subscription changes)
- **Use case**: Update user-specific UI, sync external services
- **Parameters**:
  - `newState`: Current EntitlementUserData
  - `oldState`: Previous EntitlementUserData (optional)
- **Return**: void

```javascript
window.storefrontHooks.entitlementChanged = (params) => {
  console.log('User entitlement changed:', params.newState);
  // Update external user profiles, analytics
};
```

### `subscriptionsChanged(params)`

- **Triggered**: When user subscriptions change, when processSubscribeAction is performed
- **Use case**: Track subscription changes, update UI
- **Parameters**:
  - `subscriptions`: Array of AppSubscription objects
- **Return**: void

```javascript
window.storefrontHooks.subscriptionsChanged = (params) => {
  console.log('Subscriptions updated:', params.subscriptions.length);
  // Update subscription-dependent features
};
```

### Advertisement Hooks

### `onAdInit()`

- **Triggered**: To initialize pre-bid scripts for ads (only for GPT)
- **Use case**: Set up ad networks, initialize bidding
- **Parameters**: None
- **Return**: void

```javascript
window.storefrontHooks.onAdInit = () => {
  // Initialize ad networks, prebid scripts
};
```

### `beforeRenderAd(params)`

- **Triggered**: Before an ad is rendered (only for GPT)
- **Use case**: Custom ad rendering logic, ad blocking
- **Parameters**:
  - `id`: Ad slot identifier
  - `slot`: Ad slot object
  - `config`: Ad configuration
  - `refresh`: Whether this is a refresh (optional)
- **Return**: boolean (true = continue with default rendering, false = skip)

```javascript
window.storefrontHooks.beforeRenderAd = (params) => {
  console.log('Rendering ad:', params.id);
  // Return false to prevent default ad rendering
  return true;
};
```

### `isAdRefreshPaused()`

- **Triggered**: To check if ad refresh should be paused (only for GPT)
- **Use case**: Pause ads during video playback, user interactions
- **Parameters**: None
- **Return**: boolean (true = pause refresh, false = allow refresh)

```javascript
window.storefrontHooks.isAdRefreshPaused = () => {
  // Check if ads should be paused
  return isVideoPlaying();
};
```

### `isAdDisabled(id)`

- **Triggered**: To check if specific ads should be disabled
- **Use case**: Disable ads on certain devices or screen sizes
- **Parameters**:
  - `id`: Ad identifier
- **Return**: boolean (true = disable ad, false = show ad)

```javascript
window.storefrontHooks.isAdDisabled = (id) => {
  // Disable ads on mobile devices
  return window.innerWidth < 768;
};
```

### UI Hooks

### `getIntersectionMargin()`

- **Triggered**: To get intersection margin for lazy loading
- **Use case**: Control when components load based on viewport proximity
- **Parameters**: None
- **Return**: string (CSS margin value, e.g., "500px")

```javascript
window.storefrontHooks.getIntersectionMargin = () => {
  // Load components 500px before they become visible
  return "500px";
};
```

## Purchase Result Types

When implementing purchase hooks, return one of these result types:

- `{ type: 'continue' }` - Proceed with the platform's purchase
- `{ type: 'cancel' }` - Silently cancel the purchase process
- `{ type: 'error', code?: ERROR_CODE }` - Cancel with error (code unused for subscriptions)

## Example Implementation

```javascript
// Complete example in custom.js
window.storefrontHooks = {
  onPurpleServiceInit: () => {
    console.log('Storefront initialized');
    // Initialize analytics, ads, etc.
  },

  onNavigationStable: (params) => {
    // Track page views
    gtag('config', 'GA_MEASUREMENT_ID', {
      page_path: params.currentUrl
    });
  },

  beforeContentPurchase: async (purchaseInfo) => {
    // Show custom purchase dialog
    const proceed = await showCustomPurchaseDialog(purchaseInfo);
    return proceed ? { type: 'continue' } : { type: 'cancel' };
  },

  entitlementChanged: (params) => {
    // Update user session in external systems
  }
};
```

