Docs
Web SDK
Dependency-free ES modules for the help centre, walkthroughs, support chat and a report widget. There is no npm install or build step.
Requirements
A current browser with ES modules, shadow DOM, native dialogs and Intl.Segmenter. Serve the SDK's src/ directory and your generated bundle as static files, and allow your site's origin on the hlpd app.
Load and mount
import { Hlpd } from "./hlpd/src/index.js";
const hlpd = await Hlpd.load({
apiKey: "hk_…",
bundleURL: "/hlpd-bundle/",
// baseURL defaults to https://api.hlpd.uk
tickets: { serverURL: "https://api.hlpd.uk", projectID: "brp_your_product" },
context: () => ({ screen_name: "Settings" }),
onEscalate: (ticketId, summary) => { /* Observe a sent ticket or handle app-owned support. */ },
});
const help = hlpd.mountHelpCenter(document.querySelector("#help"));
const support = hlpd.mountSupport(document.querySelector("#support"));
// help.unmount(); support.unmount(); hlpd.destroy();Both mounts render into shadow roots with the bundled stylesheet. Light and dark themes follow prefers-color-scheme, and dialogs keep drafts and close with Escape. Topics and search use only bundle files, so the help stays available when the API cannot be reached.
Hlpd.load fetches the manifest and lexical index first and loads the embedding files in the background. await hlpd.semanticReady waits for hybrid search (it resolves to false if embeddings are absent or invalid), and hlpd.orderReady resolves to the fetched order, or null.
Search and suggestions
const hits = hlpd.search("pause reminders", 5);
// [{ id, title, summary, score, lexical, semantic }]
const suggestions = hlpd.suggestions("reminders keep buzzing at night");Suggestions in your own form
Give the SDK your input or textarea and an empty element above it. Suggestions wait 400 ms and need three content words plus the bundle's confidence threshold.
const attachment = hlpd.attachSuggestions(descriptionInput, suggestionsElement, {
surface: "bug_report",
});
// After your own report submission succeeds, before resetting the form:
attachment.reportFiled({ ticketId: response.ticket_id });
// attachment.detach();Opening a card shows the topic and its walkthrough modes in an overlay. reportFiled records the suggested and opened topics with your ticket id and resets tracking for the next report.
Your own chat UI
hlpd.chat is an observable headless model. Its entries contain { id, role, text, attachments, isError? }, with attachment types topic, walkthrough, escalation and ticket.
const unsubscribe = hlpd.chat.subscribe(model => renderYourChat(model));
await hlpd.chat.send("Where can I pause reminders?");
hlpd.chat.openSupport();
hlpd.chat.updateDraft(draftId, { title: "Reminders won't stop", summary: "Details…" });
await hlpd.chat.sendSupport(draftId);
hlpd.chat.cancelDraft(draftId);
hlpd.chat.openedTopic(topicId);
hlpd.chat.viewedWalkthrough(topicId);
// hlpd.chat.reset(); unsubscribe();With no tickets configuration, the chat says "Support is handled by the app" and calls onEscalate(null, summary). With tickets, it calls onEscalate only after Send succeeds. hlpd.agent.ask(question) returns raw agent replies without submitting tickets.
Tickets
hlpd.tickets exposes list(), ticket(id), refresh(), refreshTicket(id), markRead(id), messages(id), reply(id, body) and subscribe(listener), plus submitReport({ title, text, manifest, context, topicsOpened, attachments }) for host forms that share the SDK inbox. Attachments are File or Blob objects: PNG, JPEG, WebP or GIF, at most 5 MB each.
Report widget
src/ and src/ also work on their own as a report and feature-request form for any page. Mark up a form and it mounts automatically:
<link rel="stylesheet" href="/hlpd/src/bug-reporter.css">
<script type="module" src="/hlpd/src/bug-reporter.js"></script>
<form class="bug-report-widget" data-bug-report-form
data-server-url="https://api.hlpd.uk" data-project-id="brp_your_project">
<input type="hidden" name="kind" value="bug">
<label>Title <input name="title" maxlength="200" required></label>
<label>What happened? <textarea name="body" required></textarea></label>
<label>Screenshot or photo <input name="attachments" type="file" accept="image/*" multiple></label>
<button type="submit">Send</button>
<div class="bug-report-status" data-bug-report-status aria-live="polite"></div>
</form>Or call mountBugReportForm(form, { serverUrl, projectId }) yourself. The widget lists the visitor's saved tickets and threads. widget/ in the SDK is a complete example with a launcher and dialog.
Walkthroughs
Pass a walkthroughs host to Hlpd.load. Modes are showMe and guideMe; the optional supports(script, mode) can return false to disable a mode. begin returns a session, and the exported createDOMSession builds one from a registry of your real elements:
import { Hlpd, createDOMSession } from "./hlpd/src/index.js";
const hlpd = await Hlpd.load({
apiKey: "hk_…",
bundleURL: "/hlpd-bundle/",
walkthroughs: {
async begin({ walkthrough, mode, container, signal }) {
if (mode === "showMe") {
const demo = createDemoLogView(); // your own view with disposable sample data
container.append(demo.root);
return createDOMSession({
root: demo.root,
registry: new Map([["record-drink", demo.recordButton]]),
isolated: true,
dispose: () => demo.root.remove(),
});
}
return createDOMSession({
root: document.querySelector("#app"),
registry: new Map([["record-drink", document.querySelector("#record-drink")]]),
});
},
},
});- Show me requires
isolated: trueand a newly created view with disposable data, mounted incontainer. Never connect it to production data. - Guide me returns the real page root. The SDK never calls
performin Guide me; actionable steps advance only after the user's own tap or typing. - The SDK never executes selectors or bundle code. Targets resolve only through your registry.
Narration audio is generated in advance by the publisher and plays by default, with Mute and Enable sound controls. Captions stay visible if the browser blocks autoplay.
Device id
The SDK generates one 16-character base62 device id per browser storage and keeps it under Hlpd.deviceID. Help requests send it as X-Hlpd-Device and reports send it as client_id. deviceID(storage) from src/ reads it.