--- title: "Adding webview customizations to the product detail page" slug: "pdp-webview-integration" updated: 2026-10-28T10:39:58Z published: 2026-10-28T10:39:58Z 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 page 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) — Covers setting up external identity providers and reading context in any Associate App webview. > - [Adding webview customizations to the cart](/v1/docs/cart-webview-integration) — Covers setting up external identity providers and reading context in the cart screen, using the `cart_top` and `cart_footer` slots. > - [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# ``` 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 <token>` | | `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://.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. To fulfill or cancel add-ons, see [Fulfilling or canceling add-ons](/v1/docs/adding-customized-products-to-a-cart#fulfilling-and-canceling-add-ons). ```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: '',    fulfillment: 'IN_STORE_HANDOVER',    price: { source: 'INTERNAL', source_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: '', 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: '' }    ]  } })) ``` ### 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` > [!WARNING] > Available from Associate App v1.83.0 An inline webview displays a header with the configured `title` and a button (labeled with `caption`) that opens the same page in full-screen mode. To hide that button and keep the webview inline-only, set `meta.allowExpansion: false`. The `allowExpansion` property defaults to `true`. Only an explicit `false` value hides the button. ## 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) ```