Helpers
Helpers are the JavaScript functions your templates call to do things — send a query, open a preview, react to an answer, submit a form, show or hide the window. You reference them from HTML events as skdwn.instance.helpers.<name>(...). They are the behavioral counterpart to the Liquid templates: the template draws the button, the helper is what runs when it's clicked. In the file browser of the Advanced editor they live under Template Helpers (settings.extensions.helpers).
How a Helper Runs
Every helper is a plain function attached to skdwn.instance.helpers. Inside, it reaches the running assistant through the skdwn.instance object and drives the UI by updating state or creating results. The methods a helper leans on most:
| Instance method | What it does |
|---|---|
updateState(partial) | Merge fields into the live state and re-render (e.g. { isLoading: true }). |
createResult(result) | Append a new exchange — { type, payload } — to the conversation. |
retrieveResult(id) / updateResult(result) | Read a result by id and write it back after mutating its payload. |
clearResults() | Wipe the stored conversation. |
sendTrace(event, props?) | Record an analytics event (feeds the dashboard). |
getPreviousConversation() | The prior turns, used to send conversational context. |
executeStreamPrompt(request) | Run a streamed query against the backend. |
sendFormRequest(formType, values) / executeProxyRequest(id, body, cb) | Submit a lead form / call a proxy endpoint. |
stopQuery() | Abort the in-flight streamed query. |
Result type values you'll create: WELCOME (greeting + hints), FORM (a lead form), and ERROR (an error bubble). Reading and writing a result's payload is how helpers persist per-answer state such as a reaction.
Built-in Helpers
Below is every built-in, what it expects, and how it behaves. Signatures match the source; when a helper takes no arguments the parentheses are empty.
Query lifecycle
sendQuery() — async, no arguments. Reads the current text from #skdwn-search-input. If it's empty it does nothing. Otherwise it flips state to { query, isLoading: true }, scrolls to the bottom, and streams the request to the backend. The request it builds carries:
userPrompt— the box's text.previousConversation— the prior turns, only when
settings.usePreviousConversation is enabled; otherwise an empty string.
additionalContext— whatever the host page passed instartOptions.additionalContext.
On success it clears the box (query: ''), resets the textarea height, and closes any open tab. On failure it keeps the text so the user can retry, clears isLoading, logs the error, and pushes an ERROR result.
onChangeQuery(event) — wire to oninput on the textarea. Writes the field's value into state.query and auto-grows the textarea. Where the browser supports CSS field-sizing: content the growth is native; elsewhere (e.g. Firefox) it falls back to adjusting the height in JS.
onPressEnter(event) — wire to onkeydown on the textarea. Enter submits (calls sendQuery() and prevents the default newline); Shift+Enter inserts a line break so the box grows to a multi-line question.
stopQuery() — no arguments. Aborts a query that's currently streaming.
hintBoxClick(element) — pass the clicked element as this. Reads the suggested question from its data-hint attribute, drops it into the search box, calls sendQuery(), and traces a HintBoxClick event.
Conversation and reactions
showHints() — no arguments. Re-shows the greeting/hints by creating a fresh WELCOME result and traces ShowHints.
clearHistory() — no arguments. Clears stored results, empties state.results, creates a new WELCOME, and traces ClearHistory.
react(resultId, reactionType, element) — record a thumbs up/down on an answer. resultId is the id of the answer's result; reactionType is 'like' or 'dislike' (element is accepted for symmetry but unused). The toggle logic: clicking the same reaction again is a no-op; clicking the opposite one resets it to neutral. It traces a Reaction event and writes payload.reaction back onto the result.
Preview modal
openPreview(event, element) — open the image/document preview. Reads data-previewsrc (the source path segment) and data-title off the element, builds ${serviceURL}/api/public/assistants/${previewsrc} — appending the accesstoken as a query parameter when one is present — and shows it in the modal via { modalOpen: true, modalContent, modalTitle }.
data-* attributes are lowercased
The DOM lowercases dataset keys, so data-previewSrc in your Liquid is read as element.dataset.previewsrc. Match the casing the helper expects.
closePreview() — no arguments. Clears the modal ({ modalOpen: false, modalContent: '' }).
Window and layout
toggleMaximized() — no arguments. Toggles #skdwn-main-container between its minimized and maximized sizes and mirrors that in state.skdwnWindowState.
toggleShowFloatingWindow(show) — show is a boolean. true hides the floating button, shows the window (restoring its last size, or forcing minimized when the floating action button is disabled), focuses the search box, and plays the open animation. false hides the window, marks it closed, and brings the button back.
handleScroll() — no arguments. Scrolls the conversation container (#skdwn-floating-window-main) to the bottom; helpers call it after adding content so the newest answer is visible.
Forms and proxies
showForm(formType) — formType is the form's id. Sets activeForm: true and creates a FORM result (with a generated UUID) that renders that form.
sendForm(formType, event) — async, wire to a form's onsubmit. Prevents the default submit, serializes the form's fields into a payload, marks the result as submitting, and calls sendFormRequest(formType, payload). On success the result is flagged submitted; on failure it pushes an ERROR result; either way it clears activeForm.
sendProxyForm(proxyId, event, callback) — async, for forms that call a proxy endpoint. Prevents the default submit, serializes the fields, flips isLoading on, and calls executeProxyRequest(proxyId, payload, callback). If the proxy returns a response it's appended as a result; errors become an ERROR result.
Rendering helpers
materialIcon(parameters) — returns safe HTML for a Material icon. Unlike the event helpers, this one produces markup, so you use it as a Liquid filter (see Helpers are also filters). parameters accepts:
iconName— the ligature name (e.g.search,close).size— CSS font-size (defaults to16px).iconStyle— an icon variant such asoutlinedorround(maps tomaterial-icons-<style>).
{% assign icon = "" | buildIconParams %} {# or pass an object you already have in scope #}
{{ icon | materialIcon }}
The value you pipe in becomes the parameters object, so in practice you call it with whatever object is in scope. Its output is already safe HTML — don't add | safe.
greet(parameters) — a tiny sample helper that returns JSON.stringify(parameters). It's there as a starting point when you add your own — open it, use Test code to pass sample JSON, and see the round-trip in the debug panel.
Calling a Helper
Bind helpers to DOM events straight from the Liquid:
<textarea id="skdwn-search-input"
oninput="skdwn.instance.helpers.onChangeQuery(event)"
onkeydown="skdwn.instance.helpers.onPressEnter(event)"></textarea>
<button onclick="skdwn.instance.helpers.sendQuery()">
{{ 'ask' | t: 'Ask' }}
</button>
Pass data to a helper through data-* attributes and read them off the element with this:
<a href="#" onclick="skdwn.instance.helpers.openPreview(event, this)"
data-title="{{ reference.title }}"
data-previewSrc="{{ state.instance.config.key }}/tools/preview/{{ reference.id }}/{{ refPage }}">
Preview
</a>
For a hint card, stash the question on data-hint and let hintBoxClick pick it up:
<button data-hint="{{ hint.text | sanitize }}"
onclick="skdwn.instance.helpers.hintBoxClick(this)">
{{ hint.text }}
</button>
Helpers Are Also Filters
Every helper — built-in and custom — is registered twice:
- As a method on
skdwn.instance.helpers.<name>, foronclick/oninput/onsubmit. - As a Liquid filter of the same name, for use inside
{{ … }}.
Which one you use depends on what the helper does. Event helpers (sendQuery, react, openPreview, …) belong on DOM events. Helpers that return a value — like materialIcon, which returns HTML — are used as filters, where the piped value becomes the helper's first argument:
{{ someValue | myFormatter }} {# equivalent to myFormatter(someValue) #}
This is why a custom helper you add for formatting can be dropped straight into {{ }} without any extra wiring.
Your Own Helpers
The Advanced editor lets you add custom helpers — JavaScript stored in settings.extensions.helpers. Each entry can be a single function or an object of functions; Seekdown evaluates them at startup and merges them onto skdwn.instance.helpers, so you call them exactly like the built-ins. If a helper fails to evaluate it's skipped (with a console warning) rather than breaking the widget.
// A helper named "trackClick"
(ref) => { window.analytics?.track('reference_click', { id: ref }); }
<a onclick="skdwn.instance.helpers.trackClick('{{ reference.id }}')">{{ reference.title }}</a>
Inside a custom helper you have the same instance API the built-ins use. To change what's on screen, call skdwn.instance.updateState({ ... }); to add a message, call skdwn.instance.createResult({ type: 'ERROR', payload: { reply: 'Something went wrong' } }); to log an analytics event, call skdwn.instance.sendTrace('MyEvent', { ... }).
Test before you save
Use Test code in the editor toolbar to run a helper with sample JSON parameters and inspect its return value in the debug panel — the fastest way to check a helper behaves before wiring it into a template.