Storefront Search SDK

Call the storefront search pipeline from your own JavaScript — typed search, instant suggestions, voice, photo and similar-product queries, with results you render yourself.

Storefront Search has two ways in.

The widget is the no-code path: you write HTML, mark it up with data-octocom-* attributes, and our script binds it to the search pipeline and renders into it. Most stores want this — it's covered on the Storefront Search page.

The SDK is this page: the same search client, exposed as a JavaScript object you call yourself. Use it when you're rendering results in your own code — a React storefront, a custom results page, a headless front end — and want the pipeline without our DOM rendering.

Both talk to the same endpoints and both are already on the page. There is no package to install.

Getting the client

The SDK ships inside the Octocom embed script you already have on your store. Once it loads on a business with Storefront Search configured, it publishes the client at window.octocomSearch.

The script loads asynchronously, so your code may run first. Handle both cases:

function withSearch(fn) {
  if (window.octocomSearch) return fn(window.octocomSearch);
  window.addEventListener("octocom:search-ready", (e) => fn(e.detail), {
    once: true,
  });
}

withSearch(async (search) => {
  const { results } = await search.search("black office chair");
  console.log(results);
});

In React, the same thing as a hook:

function useOctocomSearch() {
  const [client, setClient] = useState(() => window.octocomSearch ?? null);
  useEffect(() => {
    if (client) return;
    const onReady = (e) => setClient(e.detail);
    window.addEventListener("octocom:search-ready", onReady, { once: true });
    return () => window.removeEventListener("octocom:search-ready", onReady);
  }, [client]);
  return client;
}

Methods

Every method returns a promise and takes an optional signal (an AbortSignal) so you can cancel on unmount.

MethodWhat it doesCost
suggest(query, opts)The search-as-you-type panel. Query completions, a few top products, category/brand chips, a count.Cheap — safe per keystroke.
search(query, opts)Full pipeline for a submitted query. Ranked results only.A full search.
searchPanel(query, opts)Full pipeline, answered in suggest's shape — results and chips, count, refinements.Same as search.
voiceSearch(blob, opts)Transcribe a recorded utterance and search for it, in one round trip.A search + transcription.
transcribe(blob, opts)Transcript only, for dictate-and-review.Transcription.
imageSearch(blob, opts)Search by photo.A search + a vision call.
similarProducts(productId, opts)"You may also like" for a product page.Cheapest — no AI call.
getUiConfig(opts)The business's dashboard-configured widget HTML. Only needed if you're doing your own binding.One indexed read.

Search-as-you-type

suggest() is built to run on every (debounced) keystroke. It's a keyword probe, not a full search — no AI call — so it's cheap and fast, and its job is to produce a dropdown of query strings, not results.

input.addEventListener("input", async () => {
  const panel = await search.suggestLatest(input.value);
  if (!panel) return; // superseded by a newer keystroke
  render(panel); // { suggestions, products, categories, brands, totalCount }
});

suggestions are full query strings ready to submit. products is a preview handful from the same probe — enough for a dropdown column, not a results page. categories and brands are navigable chips. totalCount is how many products match the typed words at all.

Submitted searches

When the shopper hits Enter, you have two choices.

search() gives you the ranked results and nothing else:

const { results, relaxed } = await search.search(query, { topK: 24 });

searchPanel() runs the same pipeline but answers in the shape suggest() uses — the results plus the chips, the count and refinements, all describing the results you're about to show:

const panel = await search.searchPanel(query, { topK: 24 });
// { products, suggestions, categories, brands, totalCount, relaxed }

Use searchPanel() when your results page has a facet rail, a "N results" headline, or "narrow your search" chips. Getting them this way is one request; the alternative — calling search() and suggest() together — is a wasted search's worth of latency, and its chips would describe the probe's result set rather than the ranked one you're displaying.

Two things to know about it:

  • It is a full search. Same cost, same rate limit, same latency as search(). It does not belong on a keystroke; that's what suggest() is for.
  • totalCount is not products.length. The ranked list is capped by topK; totalCount answers "how many products match at all", which is what a results headline wants.

relaxed: true on either means exact retrieval came back thin and the results were recovered by relaxing the query — label them "similar products" rather than presenting them as exact matches.

Avoiding stale results

Every ranking method has a *Latest twin — searchLatest, suggestLatest, searchPanelLatest — that aborts whatever request was in flight and resolves null for a response that has since been superseded:

const response = await search.searchPanelLatest(query);
if (response === null) return; // a newer query is already running

They share one slot, so a keystroke's suggestLatest and an Enter's searchPanelLatest supersede each other — the right behavior for a single search box. Render every non-null result unconditionally and out-of-order responses can't flash over fresh ones.

Errors

Failures throw OctocomSearchError with a kind:

kindMeaning
rateLimitedHTTP 429. Back off — the caps are per-IP and per-business.
httpAny other non-2xx. 401 usually means the origin allowlist.
networkThe request never completed.

Aborts (yours, or a superseded *Latest call) surface as a DOMException named AbortError, not as an OctocomSearchError — so "the shopper typed another character" stays distinguishable from "search is down".

On a live storefront, prefer leaving the previous results on screen to clearing them on error.

Panel data on your results page

If you're using the widget rather than the SDK, you can still have the chips and count beside your results — add their markers to your Results template (or your Overlay template) and the widget switches to the panel route automatically. No markers, no extra request: templates that don't ask for the data don't pay for it.

MarkerBehavior
data-octocom-panel-categories-container + -category-templateCategory chips for the current results. data-octocom-field="text" / "count" / "url".
data-octocom-panel-brands-container + -brand-templateBrand chips, same fields.
data-octocom-panel-total-countSingle element — total keyword matches for the query, hidden while unknown. Not the number of cards shown.
data-octocom-refinements-container + -refinement-template"Narrow your search" chips. data-octocom-field="text" / "role". Clicking one runs the refined query.
data-octocom-panel-categories-section / -brands-section / -refinements-sectionOptional wrapper around a section's heading + container, hidden as a unit when that section is empty.

The refinement markers are deliberately named differently from the dropdown's data-octocom-suggestion-* pair, so a results template can carry both without the widget mistaking one for the other.

Chips are only ever shown for values your catalog feed gives a navigable URL for. If categories comes back empty, your feed carries category names but no category links — the same vocabulary still reaches shoppers through refinements, which need no URL.

Choosing a surface

You're buildingUse
A search bar styled with your own HTMLThe widget — no JavaScript at all.
A React/Vue results page in the browserThis SDK.
Server-rendered results, a mobile app, a backend jobThe REST API.

On this page