The best documentation experience is the one that never makes the user leave. Instead of a "Docs" link that opens a separate site where they hunt through a sidebar, an embedded search widget lets them ask a question right where they are — in your app, in your docs page — and get an answer inline, with sources. It's a small piece of UI over your knowledge base's search API, and it dramatically shortens the path from "I have a question" to "I have an answer." Here's how to build one and embed it anywhere.

What the widget is
At its core, the widget is three things:
- A trigger — a search box or a keyboard shortcut (⌘K / Ctrl-K is the expected convention) that opens the widget from anywhere in your app.
- A query interface — the user types a natural-language question.
- An answer surface — the widget calls your knowledge base's search API (and optionally an LLM to synthesize an answer), then renders the result inline with source links.
Everything else is polish. The substance is: capture a question, hit your search API, render the answer without navigating away.
Step 1: The API call
The widget's engine is a call to your knowledge base's search endpoint. Hybrid search is the right choice — docs queries mix natural-language intent with exact terms (API method names, error codes, feature names), and hybrid handles both:
async function search(query) {
const res = await fetch(`${KB_API}/knowledge-bases/${KB_ID}/search/hybrid`, {
method: "POST",
headers: { "X-API-Key": PUBLIC_KEY, "X-Organization-Id": ORG_ID },
body: JSON.stringify({ query, limit: 5 }),
})
return (await res.json()).results // [{ content, source, url, score }]
}
You have two rendering modes to choose from:
- Results mode — show the retrieved passages directly as ranked results with links. Simplest, fast, no LLM cost, and great when users mostly want to jump to the right doc.
- Answer mode — pass the retrieved passages to a model to synthesize a direct answer with citations. Better UX for "just tell me," at the cost of an LLM call. You can stream this for responsiveness.
Many widgets offer both: a synthesized answer at the top, the source passages below it.
Step 2: The widget UI
A minimal but real widget: a modal triggered by ⌘K, an input, and a results area.
function DocsWidget() {
const [q, setQ] = useState("")
const [results, setResults] = useState([])
const [open, setOpen] = useState(false)
useHotkey("mod+k", () => setOpen(true))
async function onSubmit() {
setResults(await search(q))
}
if (!open) return null
return (
<Modal onClose={() => setOpen(false)}>
<input value={q} onChange={e => setQ(e.target.value)}
onKeyDown={e => e.key === "Enter" && onSubmit()}
placeholder="Ask a question…" autoFocus />
<ul>
{results.map(r => (
<li key={r.url}>
<a href={r.url}>{r.source}</a>
<p>{r.content.slice(0, 160)}…</p>
</li>
))}
</ul>
</Modal>
)
}
For answer mode, replace the results list with a streamed synthesized answer (server-sent events make this feel instant) followed by the source links.
Step 3: Embedding it anywhere
To make the widget droppable into any page — including apps you don't control the framework of — package it as a self-contained script that mounts itself:
<script src="https://yourapp.com/docs-widget.js"
data-kb-id="kb_..." data-key="pk_..."></script>
The script injects the widget, wires up the ⌘K trigger, and talks to your search API. This is the pattern that lets a widget live on a marketing site, inside a web app, or in a docs portal with one line of markup. Keep the bundle small and isolate its styles (shadow DOM is ideal) so it doesn't collide with the host page.
Details that matter
- Use a scoped public key. The widget runs in the browser, so its credentials are exposed. Use a key scoped to read/search only on the specific knowledge base — never a key that can modify content or reach other data. Treat it as public because it is.
- Scope with metadata. If the widget serves a specific product or section, filter retrieval to that scope so results stay on-topic. A widget on the "Billing" docs can prioritize billing content.
- Debounce and show state. Debounce input so you're not firing a search on every keystroke, and show a loading state so the widget feels responsive.
- Instrument it. Log queries (especially zero-result ones) — they're a direct readout of what users want and what your docs don't cover. This is some of the most valuable product feedback you'll get.
- Handle "no good answer" gracefully. When retrieval comes up empty, say so and offer a fallback (contact support, browse docs) rather than showing nothing or a fabricated answer.
The payoff
An embedded search widget changes the economics of documentation. Instead of users bouncing to a separate docs site, losing their place, and often giving up, they get answers inline in seconds and stay in your product. Support load drops because self-service actually works. And you get a stream of real queries showing exactly what users struggle with. It's a small build — a search box, an API call, a bit of embedding glue — sitting on top of the genuinely hard part (the knowledge base and its retrieval), which is already done for you. Put the answer where the question is asked, and both your users and your support team feel the difference.