Hooks and Live Search

Beyond the built-in helpers, the assistant exposes a handful of hooks — helpers whose defaults do nothing, so the widget behaves exactly as before until you override them. You override them the same way as any helper (in settings.extensions.helpers, or by assigning skdwn.instance.helpers.<name> at runtime from the embedding page). The core calls them at specific moments, so you don't have to wire anything into a template.

Event Hooks

HookFiresReceives
onQuerySubmitted(query)Right after the user submits a question.query — the submitted text.
onResponseReceived(result)When an answer arrives.result{ query, reply, references }.
onFormSubmitted(formId, values)After a form is submitted.formId and the submitted values object.

They return nothing — use them for side effects like analytics, your own UI, or notifying the host page. For example, to forward every question and answer to your analytics:

// settings.extensions.helpers
{
  onQuerySubmitted: (query) => window.analytics?.track('assistant_question', { query }),
  onResponseReceived: (result) =>
    window.analytics?.track('assistant_answer', {
      query: result.query,
      sources: (result.references || []).length,
    }),
  onFormSubmitted: (formId, values) =>
    window.analytics?.track('assistant_form', { formId, ...values }),
}

The defaults are no-ops

Each hook ships as an empty function, so leaving it alone changes nothing. You only pay for what you override.

Live Search — searchProvider

searchProvider turns the search box into an instant-suggestions box: as the user types, the assistant calls your provider (debounced) and renders whatever it returns as a dropdown under the box — before they ever submit a full query. It's off until you supply one.

Contract

// (query: string, ctx: { assistantId }) => Promise<Item[]> | Item[] | undefined
searchProvider: async (query) => {
  const res = await fetch(`https://example.com/search?q=${encodeURIComponent(query)}`);
  const rows = await res.json();
  return rows.map((r) => ({
    title: r.name,        // shown text (and the query run if the row has no url)
    url: r.href,          // optional — see below
    summary: r.excerpt,   // optional secondary line
    image: r.thumbnail,   // optional thumbnail
    metadata: { price: r.price }, // optional free-form, passed to the template
  }));
}

Each item is { title, url?, summary?, image?, metadata? }:

  • title — the text shown, and the query that runs when a URL-less row (or its Ask

button) is clicked.

  • url — optional. Present → the row is a link and clicking it navigates (same tab).

Absent → clicking the row runs the title as a query.

  • summary — optional second line.
  • image — optional thumbnail.
  • metadata — optional free-form object (price, badges, rating…). The whole item is

passed to the suggestion template, so anything you put here is available when you restyle the rows.

What the user sees

  • While the provider runs, a loading skeleton shows in the dropdown, so the box is never

blank.

  • Every row also has an "Ask" button that opens the assistant with that row's title as

the query — handy for turning a result into a richer question.

  • The dropdown closes when the user clicks outside it or opens the overlay.

Limit the results with settings.suggestionsLimit (default 5). It's a settings-only value — set it in the assistant configuration; there's no UI control.

Restyle the rows by overriding the suggestion template (one row) — the outer suggestions container stays untouched. The whole item is in scope as item, so you can render your metadata:

{% comment %} settings.extensions.templates.suggestion {% endcomment %}
<div class="skdwn-suggestion-row" role="option">
  <a class="skdwn-suggestion" href="{{ item.url }}" onclick="skdwn.instance.helpers.dismissSuggestions()">
    <span class="skdwn-suggestion-text">
      <span class="skdwn-suggestion-title">{{ item.title | escape }}</span>
      {% if item.metadata.price %}<span class="skdwn-suggestion-summary">{{ item.metadata.price }}</span>{% endif %}
    </span>
  </a>
  <button type="button" class="skdwn-suggestion-ask" data-query="{{ item.title | escape }}"
          onclick="skdwn.instance.helpers.askSuggestion(this)">{{ 'ask' | t: 'Ask' }}</button>
</div>

Forward the visitor's identity to your search service

A searchProvider runs in the browser, so it can call any endpoint the page can reach. If your search needs the logged-in user, pass a token through startOptions.params and read it from your provider — the same mechanism linked services use.

Go Deeper

Contact us

Still need help?

Tell us what you want your website assistant to answer. We will help you map the right content, controls, and launch path.