Install Search on a headless storefront
This page installs Search on a storefront that is not a Shopify theme: a headless front, a custom site, or any stack where you write the frontend yourself. On a Shopify theme, follow Install Search on Shopify instead.
Search is a keyword search over your Dialog catalog: it matches the words of the query against your products, with typo tolerance, synonyms and pinned products. You have three ways to plug it in, from the most to the least ready-made:
| Option | Use it when |
|---|---|
| React or Vue components | Your storefront runs on React or Vue 3: a search bar and a results panel, ready to drop in. |
| SDK search controller | Any other framework or vanilla JS: the SDK runs the search, you render the results. |
| REST API | You call Search from your own server or client, without the SDK. |
Before you start
Section titled “Before you start”- Your public API key: every option authenticates with the Dialog public API key. See Where to find your API keys. Never use the private key in a storefront.
- Your catalog in Dialog: Search returns the products of your Dialog catalog. Check that it is synced first (Catalog integration).
- A language and a currency: an ISO 639-1 language code (
en) and an ISO 4217 currency (USD). Search keeps one index per language and currency; a regional locale such asen-USis rejected as a language.
React or Vue components
Section titled “React or Vue components”@askdialog/dialog-react and @askdialog/dialog-vue ship a search bar (DialogSearchBar) and a results panel (DialogSearchResults) on top of the SDK search controller: useDialogSearch({ client, language, currency }) creates the controller, the two components render it. The panel shows the products (and collections, if you ask for them), emphasizes the query match in each title and paginates.
The code sample and the full reference of the hook and the components (options, props, router adapter, collections column) live with each package:
SDK search controller
Section titled “SDK search controller”Outside React and Vue, use createSearchController from @askdialog/dialog-sdk. The controller sends the requests and keeps the search state; you render the results from that state.
import { createSearchController, Dialog } 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), },})
controller.subscribe((state) => render(state)) // your renderinginput.oninput = () => controller.setQuery(input.value)The SDK reference: createSearchController gives the full wiring (impressions, clicks, pagination), the options, the methods and the state.
REST API
Section titled “REST API”The SDK and the components call the endpoint POST /public/search/lexical. Call it directly when you do not use the SDK, for example from your own server.
POST https://api.askdialog.ai/public/search/lexicalContent-Type: application/jsonx-dialog-api-key: YOUR_PUBLIC_API_KEYAuthentication
Section titled “Authentication”Send your public API key in the x-dialog-api-key header. A request without a key, or with an unknown key, answers 401.
The endpoint is rate limited per IP address: 20 requests per second. Past that limit it answers 429 with a Retry-After header.
Request
Section titled “Request”The body holds one entry per index to search, in requests (1 to 10 entries). All the entries must share the same query, language and currency.
{ "requests": [ { "indexName": "products_en_usd", "query": "running shoes", "page": 0, "hitsPerPage": 20 }, { "indexName": "collections_en_usd", "query": "running shoes", "hitsPerPage": 5 } ]}| Field | Type | Required | Description |
|---|---|---|---|
indexName |
string |
Yes | <index>_<language>_<currency>. <index> is products, collections, articles or pages; the currency is lowercase (products_fr_eur). Only request an index your catalog has: a missing one fails the whole request. |
query |
string |
Yes | The shopper’s query: 2 to 200 characters, trimmed. |
page |
number |
No | Zero-based page. Default 0. |
hitsPerPage |
number |
No | Results per page, from 1 to 100. Default 20. |
facetFilters |
array |
No | Value filters, on products entries only. See Filters. |
numericFilters |
array |
No | Number filters, on products entries only. See Filters. |
An unknown field in the body or in an entry answers 400.
Filters
Section titled “Filters”Filters narrow a products entry on the filterable attributes of your catalog, the ones you publish in Product filters: a filter on any other attribute answers 400. Both fields are a list of groups: the groups combine with AND, the filters inside a group with OR.
{ "indexName": "products_en_usd", "query": "jacket", "facetFilters": [[{ "attribute": "color", "value": "black" }, { "attribute": "color", "value": "navy" }]], "numericFilters": [[{ "attribute": "price", "from": 50, "to": 150 }]]}- A facet filter is
{ attribute, value, negated? }. Setnegated: trueto exclude the value. - A numeric filter is either
{ attribute, operator, value }, withoperatoramong<,<=,=,!=,>=,>, or a range{ attribute, from, to }.
Response
Section titled “Response”The response holds one result per entry, in results, in the order of the request.
{ "results": [ { "index": "products_en_usd", "hits": [ { "objectID": "product-123", "title": "Trail running shoes", "url": "https://mystore.com/products/trail-running-shoes", "handle": "trail-running-shoes", "imageUrl": "https://cdn.mystore.com/trail.jpg", "priceRange": { "min": { "amount": "89.90", "currencyCode": "USD" }, "max": { "amount": "99.90", "currencyCode": "USD" } } } ], "nbHits": 42, "page": 0, "nbPages": 3, "hitsPerPage": 20, "processingTimeMS": 12, "query": "running shoes", "queryID": "5f0c..." } ]}Each result:
| Field | Type | Description |
|---|---|---|
index |
string |
The indexName of the entry. |
hits |
array |
The records of this page. |
nbHits |
number |
Total number of matches, all pages included. |
page |
number |
Zero-based page returned. |
nbPages |
number |
Number of pages. |
hitsPerPage |
number |
Page size applied. |
processingTimeMS |
number |
Search time, in milliseconds. |
query |
string |
The query searched. |
queryID |
string |
Identifier of this search, one per entry. |
filters |
array |
Optional: the filters applied to the entry, one block per source. |
Each hit carries objectID (the record’s identifier) and, when known, its display fields: title, url, handle, imageUrl, and for products a priceRange (min and max, each an amount as a decimal string such as "24.90" and an optional currencyCode).
Product hits can also carry their purchase data, all optional:
| Field | Type | Description |
|---|---|---|
totalVariants |
number |
Number of variants. |
tracksInventory |
boolean |
Whether the product tracks its stock. |
totalInventory |
number |
Stock across variants. |
options |
array |
Product options: { name, values }. |
variants |
array |
Variants: id, title, url, imageUrl, price, compareAtPrice, inventoryQuantity, inventoryPolicy, selectedOptions ({ name, value }). |
A variant’s stock is not stored as a flag: read it from inventoryQuantity and inventoryPolicy, against the product’s tracksInventory.
Errors
Section titled “Errors”| Status | Cause |
|---|---|
400 |
Invalid body: unknown field, query under 2 characters, entries with different queries, languages or currencies, or a filter on an attribute that is not filterable. |
401 |
Missing or unknown public API key. |
404 |
Index name that does not exist, for example a malformed name or a missing currency suffix (products_en). |
429 |
Rate limit reached: retry after the Retry-After delay. |
502, 503, 504 |
Search is temporarily unavailable: retry later. |
Error bodies carry a statusCode and a message.
Verify your install
Section titled “Verify your install”On your storefront:
- Type a product name in your search field: Dialog products appear.
- Type it with a typo: the right products still appear.
- Click a result: its product page opens.
- Change page with the pagination: the next results load.
If no result ever appears, check your public API key and that your catalog is synced, then the network tab: a 404 points to the index name, a 401 to the key.
Related
Section titled “Related”- Search overview: what Search does and the beta terms.
- SDK reference: Search:
client.search()andcreateSearchControlleroptions, methods and state. - React components: Storefront search and Vue components: Storefront search.
- Developers: the API, SDK and component packages.
