Skip to content

Ask the docs

Answers come from this documentation only, with sources. For account-specific issues, the docs route you to the right owner.

What can I help you find?

You are chatting with an AI assistant. It can make mistakes, so double-check important information.

SDK reference

@askdialog/dialog-sdk is the core SDK for embedding Dialog in custom stacks. It handles communication with the Dialog API, the assistant lifecycle, tracking, and exposes everything you need to build a UI around it.

From npm:

Terminal window
npm install @askdialog/dialog-sdk
# or
pnpm add @askdialog/dialog-sdk
# or
yarn add @askdialog/dialog-sdk

From CDN (no bundler):

<script src="https://d2m6yt8rnm4dos.cloudfront.net/dialog-sdk.X.Y.Z.min.js"></script>

When loaded via CDN the SDK is exposed as window.DialogSDK.

import { Dialog, type SimplifiedProduct } from '@askdialog/dialog-sdk'
const client = new Dialog({
apiKey: 'YOUR_PUBLIC_API_KEY',
locale: 'en',
currency: 'USD',
callbacks: {
addToCart: async ({ productId, quantity, variantId, currency }) => {
// Add the product to your cart and update your UI
},
changeCartQuantity: async ({ productId, variantId, quantity }) => {
// Optional: set that cart line to `quantity` and update your UI
},
getProduct: async (productId, variantId): Promise<SimplifiedProduct> => {
// Fetch the product from your backend and return it in SimplifiedProduct shape
},
},
})
Option Type Required Description
apiKey string Yes Your Dialog public API key.
locale string Yes Active locale (e.g. 'en', 'fr', 'es').
callbacks.addToCart (params) => Promise<void> Yes Called when a user clicks add-to-cart inside the assistant. You handle the actual cart mutation and UI refresh.
callbacks.getProduct (productId, variantId?) => Promise<SimplifiedProduct> Yes Called to render product cards inside the assistant. Return your product in SimplifiedProduct shape.
callbacks.changeCartQuantity (params) => Promise<void> No Called when the shopper steps the quantity of a line the assistant just added. quantity is the line’s new absolute quantity. Omit it and the assistant hides its quantity stepper.
currency string Yes Store currency, ISO 4217 (e.g. 'USD', 'EUR'), independent of the locale. Read back as client.currency; Search uses it to pick the index.
callbacks.addToCart (params) => Promise<void> No Called when a user clicks add-to-cart inside the assistant. You handle the actual cart mutation and UI refresh.
callbacks.getProduct (productId, variantId?) => Promise<SimplifiedProduct> No Called to render product cards inside the assistant. Return your product in SimplifiedProduct shape.
theme Theme No Override visual theme (colors, fonts, CTA shape).
userId string No Stable user ID for your visitor. If omitted, Dialog auto-generates one and persists it.
product { id: string; variantId?: string } No The product of the current page. Grounds questions that carry no product of their own (floating bookmark, resume surface, free text) on the product being viewed. See Declaring the current product.
disableAddToCart boolean No Hides Dialog add-to-cart for this widget instance/session (default false). See “Disabling add-to-cart” below.
analyticsConsent 'granted' No Declares the analytics consent as granted, for sites with no cookie banner. Omitted by default. See Declaring the analytics consent.

callbacks can be omitted in a search-only integration (see Search): only the assistant needs them, and the client’s getProduct() and addToCart() throw a configuration error when their callback is missing.

Set disableAddToCart: true to suppress Dialog add-to-cart for a specific session, for example a B2B storefront that hides purchasing actions when a B2B customer is logged in. Evaluate your own condition at initialization:

const client = new Dialog({
apiKey: 'YOUR_PUBLIC_API_KEY',
locale: 'en',
currency: 'USD',
disableAddToCart: isB2BCustomerLoggedIn, // per session
callbacks: { /* ... */ },
})

When enabled:

  • the assistant hides the add-to-cart CTA on recommendation and conversational product cards;
  • callbacks.addToCart is never called and no add-to-cart event is tracked;
  • product links and recommendation browsing stay available.

Omit it (or set false) to keep the current behavior. The flag is read at initialization, so it applies for the lifetime of that widget instance (a login-state change takes effect on the next page load).

By default the assistant sends analytics events (add-to-cart, checkout, assistant usage) only once it detects a consent signal on the page: Google Consent Mode, TCF v2 or OneTrust. A site with no cookie banner emits no such signal, so no event is sent for any of its visitors and the dashboard stays empty.

On those sites, set analyticsConsent: 'granted':

const client = new Dialog({
apiKey: 'YOUR_PUBLIC_API_KEY',
locale: 'en',
currency: 'USD',
analyticsConsent: 'granted', // no cookie banner on this site
callbacks: { /* ... */ },
})

The option works like a Google Consent Mode default state:

  • analytics is on from the first page view, for every visitor;
  • a refusal coming from a consent platform present on the page still wins: a Google Consent Mode update set to denied, or OneTrust loaded without the performance group (C0002), turns analytics off for that visitor;
  • window.dialog.audit.consent reports detectedCmp: 'declared' when the option is what enabled analytics.

Whether consent can be assumed for your visitors is your compliance decision. Omit the option to keep the default detection.

Pass product on product pages so the assistant always knows which product the visitor is looking at. Questions asked without an explicit product (from the floating bookmark, the resume surface or the free-text input) are then answered in the context of that product, including after the visitor navigates from one product page to another:

const client = new Dialog({
apiKey: 'YOUR_PUBLIC_API_KEY',
locale: 'en',
currency: 'USD',
product: { id: 'product-123', variantId: 'variant-456' }, // variantId optional
callbacks: { /* ... */ },
})

On single-page storefronts, update the declaration on client-side navigation:

client.setCurrentProduct('product-456', 'variant-789') // variantId optional
client.clearCurrentProduct() // left for a non-product page

The id must match the product ID of your Dialog catalog feed. Without a declared product, questions asked outside a product click are answered without product context.

Because your code initializes Dialog, it must run on every route of your store, not only product, listing, and home pages. Two things depend on Dialog being present on the page:

  • Proactive surfaces (Smart Discovery’s modal and AI bar) only appear where Dialog is loaded, so a configuration targeting content pages never fires if the SDK isn’t initialized there, even when its URL pattern matches.
  • Tracking (sessions, add-to-cart, checkout) is captured only on pages where Dialog runs: routes without it silently drop analytics.

A common mistake is wiring initialization only into product, listing, and home page flows. Initialize Dialog globally instead (e.g. in a shared layout or root template), so it runs on every page type.

client.sendProductMessage({
question: 'Is this top runs small?',
productId: 'product-123',
productTitle: 'Linen V-neck top',
selectedVariantId: 'variant-456', // optional
})
client.sendGenericMessage({
question: 'What is your return policy?',
})

Get the AI-generated suggestion list for a given product. Useful if you build a custom suggestions UI on your PDP rather than using our DialogProductBlock component.

const suggestions = await client.getSuggestions('product-123')
// {
// questions: [{ question: '...' }, ...],
// assistantName: 'Your expert',
// inputPlaceholder: 'Ask any question...',
// description: 'Ask any question about this product'
// }
const info = client.getLocalizationInformations()
// { countryCode: 'US', formatted: 'en-US', language: 'English', locale: 'en' }

The SDK runs Dialog Search, the keyword search over your catalog. The React and Vue components and the REST endpoint are covered in the Search integration guide.

Search reads one index per language and currency. Build the names with searchIndexName(index, language, currency):

import { searchIndexName } from '@askdialog/dialog-sdk'
searchIndexName('products', 'fr', 'EUR') // 'products_fr_eur'
  • index: 'products', 'collections', 'articles' or 'pages'.
  • language: lowercase ISO 639-1 code ('fr'). A regional locale such as 'fr-FR' throws.
  • currency: ISO 4217 code ('EUR'), lowercased in the name.

client.search(request, options?) sends one request to the search API, as is: no debounce, no cancellation of outdated requests, no analytics. Use it for one-off searches; for a search field, use createSearchController.

const response = await client.search({
requests: [
{
indexName: searchIndexName('products', 'en', client.currency),
query: 'running shoes',
page: 0, // optional, zero-based (default 0)
hitsPerPage: 20, // optional, 1 to 100 (default 20)
},
],
})
// response.results[0].hits: [{ objectID, title?, url?, handle?, imageUrl?, priceRange? }]

options.signal (an AbortSignal) cancels the request, which then rejects with the native AbortError. An HTTP error rejects with DialogSearchError (status and message). The request and response fields are detailed in the REST endpoint reference.

createSearchController(options) runs an interactive search: it debounces keystrokes, cancels outdated requests and ignores their late responses, paginates, retries, and records search analytics (result impressions and clicks). It renders nothing: you subscribe to its state and render the results.

import { createSearchController, Dialog, SearchStatus } from '@askdialog/dialog-sdk'
const client = new Dialog({ apiKey: 'YOUR_PUBLIC_API_KEY', locale: 'en-US', currency: 'USD' })
const controller = createSearchController({
search: (request, options) => client.search(request, options),
language: 'en',
currency: client.currency,
analytics: {
surface: 'search_page',
trackViewSearchResults: (params) => client.trackViewSearchResults(params),
trackSelectSearchResult: (params) => client.trackSelectSearchResult(params),
},
navigate: (url) => router.push(url), // optional
sections: [{ index: 'collections', hitsPerPage: 5 }], // optional
})
const unsubscribe = controller.subscribe((state) => {
if (state.status === SearchStatus.SUCCESS) {
renderCards(state.response.hits).forEach((card, index) => {
controller.observeResult(card, index)
card.onclick = (event) => {
if (controller.selectResult(index)) event.preventDefault()
}
})
}
})
input.oninput = () => controller.setQuery(input.value)
form.onsubmit = (event) => {
event.preventDefault()
controller.submit(input.value)
}
nextButton.onclick = () => controller.setPage(controller.getState().page + 1)
// On unmount
controller.dispose()

language is the ISO 639-1 part of the client’s locale ('en' for 'en-US'): pass it explicitly, a regional locale is rejected.

Options are fixed when the controller is created: create a new controller to change the language or the currency.

Option Type Required Default Description
search (request, options?) => Promise<SearchResponse> Yes - Sends a request. Pass client.search, bound to the client, or your own transport.
language string Yes - Lowercase ISO 639-1 code, e.g. 'en'.
currency string Yes - ISO 4217 code, e.g. client.currency.
analytics SearchControllerAnalytics Yes - { surface, trackViewSearchResults, trackSelectSearchResult }. surface is where results are displayed: 'search_page', 'autocomplete', 'smart_discovery' or 'ai_bar'. Pass the client’s trackViewSearchResults and trackSelectSearchResult.
navigate (url, hit) => void No - Router adapter, called after a selection is recorded. Omit it to let your result links navigate natively.
debounceMs number No 250 Keystroke debounce, in milliseconds.
hitsPerPage number No 12 Products per page.
sections SearchSection[] or () => SearchSection[] No [] Extra indices searched alongside the products, e.g. [{ index: 'collections', hitsPerPage: 5 }]. Only request an index your catalog has: a missing one fails the whole search.
Method Description
setQuery(query) Searches after the debounce delay. A query under 2 characters (after trimming) resets the state to idle.
submit(query) Searches immediately.
setPage(page) Loads a zero-based page of the current query. A new query always starts at page 0.
retry() Replays the last request, when the state is error.
observeResult(element, index) Records the impression of state.response.hits[index] once element is visible.
selectResult(index, options?) Records the click on state.response.hits[index], then calls navigate. Returns true when navigate ran: call preventDefault() on the click then. Pass { navigate: false } for middle-clicks and modified clicks.
subscribe(listener) Calls listener(state) on every state change. Returns the unsubscribe function.
getState() Returns the current state.
dispose() Cancels pending requests and removes listeners and observers.
Field Type Description
status 'idle' | 'loading' | 'success' | 'empty' | 'error' empty is a successful search with no result. Compare with the SearchStatus constants.
query string Last query searched.
page number Zero-based page of the last request.
response SearchResult Optional. Products result: hits, nbHits, page, nbPages, hitsPerPage, query, queryID. Kept while loading, cleared on error.
sections object Optional. Results of the extra indices, keyed by index (state.sections?.collections).
error unknown Optional. The error of the last request, when status is error.

The SDK auto-tracks assistant interactions (open / close / message / add-to-cart from assistant). Use the methods below for events that happen outside the assistant.

client.registerAddToCartEvent({
productId: 'product-123',
quantity: 1,
currency: 'EUR',
variantId: 'variant-456',
price: 29.99,
})
client.registerSubmitCheckoutEvent({
productId: 'product-123',
quantity: 1,
currency: 'EUR',
variantId: 'variant-456',
})

Call these whenever the customer adds to cart or completes checkout without going through the Dialog assistant, so the dashboard can attribute conversion correctly. See Tracking: SDK custom for full guidance.

const unsubscribe = client.onAssistantEvent((event) => {
// event.type, event.payload
})
// Later
unsubscribe()

Available event types (full guidance in Tracking: SDK custom):

  • userOpenedAssistant
  • userClosedAssistant
  • userSentMessage
  • userClickedOnProductCard
  • userOpenedRecommendation
  • userAddedToCart
  • userSendPositiveFeedback
  • userSendNegativeFeedback

Pass a theme object on construction:

const client = new Dialog({
apiKey: 'YOUR_PUBLIC_API_KEY',
locale: 'en',
currency: 'USD',
theme: {
backgroundColor: '#ffffff',
primaryColor: '#000000',
ctaTextColor: '#ffffff',
ctaBorderType: 'rounded', // 'straight' | 'rounded'
capitalizeCtas: false,
fontFamily: 'Inter, sans-serif',
highlightProductName: true,
},
callbacks: { /* ... */ },
})

The title, description, and content keys on theme only apply to the Vue / React components. The vanilla SDK doesn’t render those (you do).

Your getProduct callback must return this shape:

interface SimplifiedProduct {
id: string
title: string
handle: string
descriptionHtml?: string
url?: string
totalInventory: number
featuredImage?: { url?: string } | null
variants: SimplifiedProductVariant[]
options?: SimplifiedProductOption[]
}
interface SimplifiedProductVariant {
id: string
displayName?: string
inventoryQuantity?: number
price: string // string, e.g. '29.99'
currencyCode: string // ISO code, e.g. 'USD'
compareAtPrice?: string | null
url?: string
selectedOptions?: { name: string; value: string }[]
image?: { url?: string } | null
}
interface SimplifiedProductOption {
id: string
name: string
position: number
values: string[]
}