# JavaScript API

> The window.ShopAssist API to open the chat, pass page context, consent, identity and cart handlers, and subscribe to events.

Source: https://shopassist.coec.dk/docs/widget/javascript-api · Updated 2026-10-01

The loader creates `window.ShopAssist`. The signatures below are read from the widget's published type definitions at build time, so they always match the widget.

```ts
interface ShopAssistApi {
  readonly __sa: true
  /** Loader semver. */
  readonly version: string
  readonly ready: Promise<{ status: WidgetStatus }>
  /**
   * Opens the panel. `draft` prefills the composer (≤ 2,000 characters); `send: true` sends the draft as the
   * shopper's first message once the panel is open, honoured only with transient user activation. Opening creates
   * a session and writes strictly-necessary sessionStorage: call it only in response to a shopper action.
   */
  open(opts?: { draft?: string; send?: boolean }): void
  close(): void
  toggle(): void
  /** Show the launcher (if hidden by `hide()`). */
  show(): void
  /** Hide launcher and bubble (the panel closes); display rules still apply. */
  hide(): void
  isOpen(): boolean
  /** Generic stores only. */
  identify(provider: IdentityProvider | null): void
  /** Manual/managed consent mode only. */
  setConsent(consent: Partial<{ preferences: boolean; analytics: boolean; marketing: boolean }>): void
  /** SPA/headless navigation. */
  setPageContext(ctx: EmbedPage): void
  /** UI locale override (not persisted). */
  setLocale(locale: string): void
  /** Generic stores only. */
  setCartHandler(handler: GenericCartHandler | null): void
  /** Returns an unsubscribe function. */
  on<E extends ShopAssistEventName>(event: E, cb: (payload: ShopAssistEvents[E]) => void): () => void
  off<E extends ShopAssistEventName>(event: E, cb: (payload: ShopAssistEvents[E]) => void): void
  /**
   * → beacon `order.completed {order_id, total_amount, currency, items_count}`; dropped unless this tab has a session
   * and the 08 §14.1 consent rule holds.
   */
  track(
    event: "order.completed",
    props: { order_id: string; total: { amount: string; currency: string }; items_count?: number },
  ): void
  /** Removes host, listeners and streams; `forget` also purges every widget storage key. */
  shutdown(opts?: { forget?: boolean }): void
}
```

## Before the loader runs [#queue]

Queue calls with `ShopAssistQueue`. The loader runs them in order once it starts, and later pushes run immediately:

```js
;(window.ShopAssistQueue = window.ShopAssistQueue || []).push(["on", "cart.added", (e) => console.log(e)])
;(window.ShopAssistQueue = window.ShopAssistQueue || []).push(["open"])
```

The loader also dispatches the window event `shopassist:ready` (with `detail.status`) when `ShopAssist.ready` resolves. Methods never throw: invalid arguments are ignored with a warning in debug mode (`data-debug="1"`).

## Opening the chat [#open]

```js
document.querySelector("#ask-button").addEventListener("click", () => {
  ShopAssist.open({ draft: "Is this available in size 42?" })
})
```

* `draft` prefills the composer (up to 2,000 characters).
* `send: true` sends the draft as the shopper's first message once the panel is open, but only directly after a real click (transient user activation), so scripts can't write for the shopper.
* Opening creates a chat session and writes strictly necessary session storage: call `open()` only in response to a shopper action.

## Methods [#methods]

| Method                                                       | Notes                                                                                      |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
| `open({ draft?, send? })`, `close()`, `toggle()`, `isOpen()` | Panel control                                                                              |
| `show()`, `hide()`                                           | Show or hide the launcher; display rules still apply                                       |
| `setPageContext(page)`                                       | Single-page navigation ([headless](/docs/install/headless))                                |
| `setLocale(locale)`                                          | Interface language override, not persisted                                                 |
| `setConsent({ preferences, analytics, marketing })`          | Manual consent mode only ([consent](/docs/widget/consent-and-cmp))                         |
| `identify(provider)`                                         | Generic stores only ([identity JWT](/docs/orders-and-identity/generic-identity-jwt))       |
| `setCartHandler(handler)`                                    | Generic stores only ([cart handler](/docs/install/generic/checkout-and-cart#cart-handler)) |
| `on(event, cb)` / `off(event, cb)`                           | [Events](/docs/widget/events); `on` returns an unsubscribe function                        |
| `track("order.completed", props)`                            | Report a completed order for attribution (dropped without a session and analytics consent) |
| `shutdown({ forget? })`                                      | Remove the widget; `forget: true` also deletes all widget storage keys                     |

## Page context [#page-context]

```ts
/** Page context the merchant passes on SPA navigation (`setPageContext`, 08 §3.2). */
interface EmbedPage {
  type?: PageTypeHint | null
  /** Platform-native id (mapped to external ids, 08 §3.4). */
  productId?: string | number | null
  variantId?: string | number | null
  categoryId?: string | number | null
  title?: string | null
}
```

## Identity provider [#identity]

```ts
/** Identity-token provider of Generic stores (08 §10.6): returns a signed JWT (03 §2.5) or null for guests. */
type IdentityProvider = (ctx: { sessionId: string; storeId: string }
```

## Another live chat on the same store [#other-chat]

If you keep another chat (Shopify Inbox, Tidio, Gorgias, Zendesk, Intercom), separate the launchers (different positions, or `data-launcher="none"` and your own button) and open the other chat from `ShopAssist.on("handoff.requested", …)` with its own JavaScript API.
