---
title: "Adding webview customizations to the product detail page"
slug: "pdp-webview-integration"
updated: 2026-05-07T13:30:51Z
published: 2026-05-07T13:30:51Z
canonical: "docs.newstore.com/pdp-webview-integration"
---

> ## Documentation Index
> Fetch the complete documentation index at: https://docs.newstore.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Adding webview customizations to the product detail page

Webviews embedded in the **Product Detail Page (PDP)** of Associate App receive context from the app at startup and can post messages back to trigger native app actions. This document describes exactly what data is available and how to use it.

> [!TIP]
> Further reading
> 
> - [Adding webview customizations using external authentication](/v1/docs/adding-webview-customizations-using-external-authentication) — This guide covers setting up external identity providers and reading context in any Associate App webview.
> - [Adding customized products to a cart](/v1/docs/adding-customized-products-to-a-cart) — An end-to-end walkthrough of a product configurator webview [using the Cart API](https://docs.newstore.net/api-portal/#/http/checkout/operations-on-carts/create-line-item).

## Delivering context

When the app opens a webview, it adds a **base64-encoded JSON payload** to the URL as a fragment (`#`):

```plaintext
https://your-webview-url.com/page#<base64-encoded-JSON>
```

Decode and parse it on the client side:

```javascript
const raw = decodeURIComponent(window.location.hash.slice(1))
const context = JSON.parse(atob(raw))
```

> [!NOTE]
> Important
> 
> Instead of accessing context via `window.NEWSTORE`, use the latest mechanism of URL fragment injection and fragment-based decode method.

The context object has the following payload:

```typescript
{
  contextProps: {
    formData: {
      // --- Product fields ---
      productId: string
      variantGroupId?: string
      sku?: string                    // deprecated, prefer externalIdentifiers.sku
      externalIdentifiers?: {
        sku?: string
        gtin?: string
        upc?: string
        ean13?: string
        jan?: string
        isbn?: string
      }
      title?: string
      brand?: string
      caption?: string
      description?: string
      price?: number
      markdownPrice?: number | null
      currencyCode?: string
      isAvailable?: boolean
      isPreorder?: boolean
      serializedInventory?: boolean
      variationColorValue?: string
      variationSizeValue?: string
      variationSizeGender?: 'male' | 'female' | 'unisex'
      variationSizeType?: string
      variationSizeSystem?: string
      variationAdditional1Name?: string
      variationAdditional1Value?: string
      variationAdditional2Name?: string
      variationAdditional2Value?: string
      variationAttributes?: {
        variationColorValues?: string[]
        variationSizeValues?: string[]
      }
      variations?: Array<{
        productId?: string
        isAvailable?: boolean
        variationColorValue?: string
        variationSizeValue?: string
        price?: number
        markdownPrice?: number | null
      }>
      images?: Array<{
        url: string
        isMain?: boolean
        title?: string
        altText?: string
        identifier?: string
        tags?: string[]
      }>
      extendedAttributes?: Array<{
        name: string
        value: string
      }>
      categories?: Array<{
        path?: string[]
        fullPath?: string
      }>
      keywords?: string[]

      // --- Session fields (always present) ---
      associateId: string             // ID of the logged-in associate
      storeId: string                 // ID of the current store
      cartId: string                  // Active cart ID (empty string if no cart yet)
    }
  }

  auth: {
    accessToken: string               // NewStore access token — use to call NewStore APIs
  }

  externalIdentities: Array<{
    identityProvider: string          // Name of the configured identity provider
    identity: string                  // Auth token for that provider
  }>

  theme: object                       // App color, spacing, and typography tokens (mobileDesign)

  dimensions: {
    top: number                       // Safe area insets (device notch / home indicator)
    bottom: number
    left: number
    right: number
  }

  enums: {
    DocumentType: {                   // Print document type constants — use with the print message
      exchangeReceipt: 'exchangeReceipt'
      hangTag: 'hangTag'
      giftReceipt: 'giftReceipt'
      inStorePickupLabel: 'inStorePickupLabel'
      invoice: 'invoice'
      packingSlip: 'packingSlip'
      returnLabel: 'returnLabel'
      returnForm: 'returnForm'
      returnReceipt: 'returnReceipt'
      salesReceipt: 'salesReceipt'
      shippingLabel: 'shippingLabel'
      fiscalDocument: 'fiscalDocument'
      transferPackingList: 'transferPackingList'
      commercialInvoice: 'commercialInvoice'
      reservationDocket: 'reservationDocket'
    }
  }

  securityToken: string               // One-time token generated per webview session
}
```

## Common field descriptions

| Field | Where | Notes |
| --- | --- | --- |
| `contextProps.formData.productId` | Product | NewStore product ID of the currently viewed variant |
| `contextProps.formData.externalIdentifiers.sku` | Product | Preferred SKU field |
| `contextProps.formData.extendedAttributes` | Product | Custom key/value pairs from the product catalog |
| `contextProps.formData.price` | Product | Display price (may be incl. or excl. tax) |
| `contextProps.formData.associateId` | Session | Current associate's user ID |
| `contextProps.formData.storeId` | Session | Current store |
| `contextProps.formData.cartId` | Session | Active cart — use with the Cart API |
| `auth.accessToken` | Auth | NewStore token — include as `Authorization: Bearer &lt;token&gt;` |
| `externalIdentities` | Auth | Third-party tokens if your integration uses an external identity provider |

## Making a call to NewStore APIs

Use `auth.accessToken` to make authenticated calls to NewStore APIs.

### Adding a product to the cart

Use the [Cart Line Item API](https://docs.newstore.net/api-portal/#/http/checkout/operations-on-carts/create-line-item) to add the current product (or any product) to the active cart. See the payload here.

```javascript
const { auth, contextProps: { formData } } = context
const baseUrl = `https://<tenant>.p.newstore.net/v0/d`

const response = await fetch(`${baseUrl}/checkout/carts/${formData.cartId}/items`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${auth.accessToken}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    product_id: formData.productId,
    fulfillment: 'IN_STORE_HANDOVER',
    price: { source: 'INTERNAL', source_id: 'default' },
  }),
})
```

### Adding an add-on to a cart item

To attach an add-on (for example, warranty information or an accessory) to a product already in the cart, use the `Location` header from the add-product response to target the parent item.

```javascript
const parentLocation = response.headers.get('Location')

await fetch(`${parentLocation}/add-ons`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${auth.accessToken}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    product_id: '<addon-product-id>',
    fulfillment: 'IN_STORE_HANDOVER',
    price: { source: 'INTERNAL', source_id: '<pricebook-id>' },
  }),
})
```

### Adding a standalone add-on service to the cart

For service products not attached to a parent item (for example, gift wrapping or insurance), use the [Add-on Line Item API](https://docs.newstore.net/api-portal#/http/checkout/operations-on-carts/create-add-on-line-item) directly.

```javascript
await fetch(`${baseUrl}/checkout/carts/${formData.cartId}/add_on_items`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${auth.accessToken}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ product_id: '<service-product-id>', quantity: 1 }),
})
```

## Posting messages back to the app

The webview can trigger native app actions via `window.ReactNativeWebView.postMessage`:

```javascript
window.ReactNativeWebView.postMessage(JSON.stringify(message))
```

### Closing the webview

```javascript
window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'close' }))
```

### Trigger printing

Each resource must have a `documentType` (use a value from `context.enums.DocumentType`) and either a `url` or `base64` payload:

```javascript
const { enums: { DocumentType } } = context

window.ReactNativeWebView.postMessage(JSON.stringify({
  type: 'print',
  message: {
    resources: [
      { documentType: DocumentType.salesReceipt, url: 'https://...' }
    ]
  }
}))
```

For base64-encoded documents (for example, generated PDFs), pass `base64` instead of `url`:

```javascript
window.ReactNativeWebView.postMessage(JSON.stringify({
  type: 'print',
  message: {
    resources: [
      { documentType: DocumentType.salesReceipt, base64: '<base64-encoded-pdf>' }
    ]
  }
}))
```

### Opening an external URL in the system browser

> [!NOTE]
> Important
> 
> The URL must start with `https://`. **Non-HTTPS URLs are ignored** without returning an error.

```javascript
window.ReactNativeWebView.postMessage(JSON.stringify({
  type: 'openExternalUrl',
  message: { url: 'https://...' }
}))
```

### Navigating to a product detail page

To navigate the associate to another product without leaving the app, use `window.open` with an Associate App deep link. The app intercepts `com.newstore.associate-one://` URLs during navigation and routes them natively:

```javascript
const productId = '1000721'

window.open(`com.newstore.associate-one://products/preview?productId=${productId}`)
```

> [!TIP]
> Note
> 
> The `openExternalUrl` postMessage handler only accepts `https://` URLs. Deep links like `com.newstore.associate-one://` must go through navigation (`window.open`, `window.location`, or an anchor tag) so the app can intercept and handle them.

Product IDs are available in `contextProps.formData.productId` (current product) and in `contextProps.formData.variations[].productId` (all variants of the current product).

### Reloading the cart in the app

After the webview modifies the cart, trigger Associate App to reload the cart view before closing. This ensures the associate sees an up-to-date cart immediately.

```javascript
const { contextProps: { formData } } = context

window.open(`com.newstore.associate-one://cart/load?cartId=${formData.cartId}`)

window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'close' }))
```

## Slots

Two slots are available on the PDP. Configure which slot the webview appears in via the Associate App configuration

| Slot ID | Position |
| --- | --- |
| `product_detail_top` | Above the action buttons list |
| `product_detail` | Below the action buttons list |

## Display modes

Webviews can be configured as:

- **Full-screen** (default) — Opens as a new screen when the associate taps a button in the app
- **Inline** — Rendered directly within the PDP scroll view. Set `meta.inline: true` in configuration, along with `meta.height` (pixels), and optionally `meta.scrollEnabled`, `meta.scrollXEnabled`, `meta.scrollYEnabled`

## Conditional rendering

A webview can be shown only when the product has a specific extended attribute. Set `meta.requiresExtendedAttribute` to the `attribute name` in the configuration. If the attribute is absent on the current product, the button or inline view is hidden automatically.

## Example: full decode

```javascript
function getWebViewContext() {
  const hash = window.location.hash.slice(1)
  if (!hash) return null
  try {
    return JSON.parse(atob(decodeURIComponent(hash)))
  } catch {
    return null
  }
}

const context = getWebViewContext()
if (!context) throw new Error('No context — not running inside the Associate App')

const {
  contextProps: { formData },
  auth,
  externalIdentities,
} = context

console.log('Product ID:', formData.productId)
console.log('SKU:', formData.externalIdentifiers?.sku)
console.log('Cart ID:', formData.cartId)
console.log('Associate:', formData.associateId)
console.log('Store:', formData.storeId)
console.log('Access token:', auth.accessToken)
```
