Suggestions & Autocomplete
How AACSearch surfaces query suggestions, popular queries, and did-you-mean as the user types.
Suggestions are short query candidates surfaced as the user types — autocomplete in the search box, "did you mean…" on no-results, and trending queries on the empty state. They are a UX layer on top of the same indexes, not a separate engine.
What "suggestions" means here
Three distinct sources, each with its own endpoint:
| Source | What it returns | Use case |
|---|---|---|
| Title prefix | Index documents whose searchable fields start with the prefix. | "wireless head…" → product titles starting with that prefix. |
| Popular queries | Past queries from SearchUsageEvent ordered by frequency. | Empty-state suggestions, "people are searching for…". |
| Spell-check | Corrected spelling derived from the index's vocabulary. | "iphine" → suggest "iphone" when the original query has few hits. |
The widget combines all three into a single dropdown; an API integrator can pick any subset.
Status
| Capability | Status |
|---|---|
| Title-prefix autocomplete via multi-search | ✅ Available |
| Popular queries (Top Queries analytics panel + API) | ✅ Available |
Spell-check / "did you mean" (/v1/projects/{projectId}/spell-check) | ✅ Available |
| Personalised suggestions (user history) | 🟡 Beta (opt-in) |
| Cross-index federated suggestions | ⏳ Roadmap |
Title-prefix autocomplete
The cleanest pattern is two searches in one round-trip via multi-search: a primary search for the result list and a tiny suggestions search for the dropdown.
const [main, suggestions] = await client.multiSearch([
{
indexSlug: "products",
q: "wireless head",
queryBy: "title,brand,description",
perPage: 20,
facetBy: "brand,categories",
},
{
indexSlug: "products",
q: "wireless head",
queryBy: "title",
perPage: 5,
includeFields: "id,title",
highlightFields: "title",
},
]);Tips:
- Keep the suggestions search cheap:
queryByonlytitle,perPage ≤ 5,includeFieldsonly the columns you render. - Use
highlightFields: "title"so you can render the matched prefix in bold. - Debounce keystrokes at 150–300 ms. Without debounce, every keystroke is one round-trip — fast but expensive.
The full multi-search response shape is in Multi-search and querying.
Popular queries
Two places to read them from:
Analytics API
const popular = await orpc.search.topQueries.call({
organizationId,
period: "30d",
limit: 10,
});Returns { query, searches, clicks, ctr, zeroResults }[] from SearchUsageEvent. Use this for an admin panel or a "trending" widget.
Empty-state suggestions
Render the top 5–10 popular queries when the search box is focused but empty. Cache the result client-side for 5–15 minutes — the underlying data only changes as new events come in.
Did-you-mean / spell-check
POST /v1/projects/{projectId}/spell-checkInput: a query string. Output: a list of corrected candidates ranked by confidence, taken from the searchable terms in the org's indexes. Use it when the primary search returns < 3 hits:
const main = await search({ q: "iphine" });
if (main.found < 3) {
const corrections = await spellCheck({ q: "iphine" });
// → ["iphone", "iphone 15", …]
}Important: the spell-check does not retry the search for the user — it returns candidates. The UI decides whether to auto-run the top candidate or show a "Did you mean: iphone?" link.
Authentication
All three sources reuse the same auth and quota model as keyword search:
- Bearer search key (
ss_search_*) — origin-restricted, rate-limited. - Scoped search tokens (HMAC) — narrow the visible documents; their
scopedFilteris AND-combined into both the primary and suggestions search (Invariant 4). - Per-org rate limits from
SearchRateLimitBucket.
A scoped token that pins indexSlug: "products" will reject a suggestions call against any other index.
Analytics grouping
Suggestion calls and the eventual click event are tied together through the query_id on the multi-search response. Pass query_id along with the click event so analytics groups the suggestion impression and the resulting click in the same funnel:
trackClick({
query_id: main.queryId,
document_id: clickedHit.id,
});See Analytics overview for the event schema.
Caching and debouncing
| Knob | Recommended value |
|---|---|
| Keystroke debounce | 150–300 ms |
| Min characters to query | 2 (1 is noisy; 3+ misses short brand names) |
| Client-side suggestion cache | LRU 20–50 entries, scoped to the current indexSlug |
| Popular-queries TTL | 5–15 minutes (refresh in the background, not on render) |
| Server cache TTL | Default Hono response cache headers; do not enable a longer cache without rate-limit-aware invalidation |
The bundled widget (packages/widget/src/index.ts) ships with debounce = 200 ms and a 30-entry LRU; align custom integrations with the same numbers.
When to skip suggestions
- Voice / scanner input — the user is committed to a query string.
- Mobile no-typing flows where the user opens the search modal pre-filled with a deep link.
- Internal admin search where exact IDs are the norm.
In each case, render the result list directly and skip the suggestions panel.
Related pages
- AI Search overview
- Multi-search and querying
- Analytics overview — measuring suggestion-driven traffic
- Widget overview — the prebuilt drop-down implementation
Semantic Search
Vector-based retrieval that matches by meaning instead of exact tokens — and how to combine it with keyword search.
Knowledge RAG Overview
Retrieval-augmented Q&A over your own documents — uploaded files, URLs, internal knowledge bases. How spaces, sources, ingestion jobs, and the ask endpoint fit together.