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.

Used Newie, our AI search tool in the docs? Take a 2-minute survey to rate your experience!

Adding webview customizations to the product detail page

Prev Next

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.

Further reading

Delivering context

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

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

Decode and parse it on the client side:

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

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:

{
  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 to add the current product (or any product) to the active cart. See the payload here.

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.

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 directly.

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:

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

Closing the webview

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:

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:

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

Opening an external URL in the system browser

Important

The URL must start with https://. Non-HTTPS URLs are ignored without returning an error.

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

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:

const productId = '1000721'

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

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.

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

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)