Headless step-by-step SDK
Build a custom UI while Pollaris owns sessions, branching, and server validation.
Use the headless SDK when you want complete control over markup and CSS. It exposes state and methods but renders nothing. It supports both linear and branching published forms.
Create a client
import { createPollarisClient } from 'https://cdn.example.com/embed/v1/index.js';
const client = createPollarisClient({
form: 'FORM_SLUG',
apiBase: 'https://api.example.com',
redirect: 'manual'
});
const unsubscribe = client.subscribe((state) => {
renderYourInterface(state);
});
await client.start({
utm_source: 'website',
utm_medium: 'embed',
language: navigator.language,
screen_w: window.innerWidth
});Render and answer
When state.status === 'ready', render state.question using your own components. Keep the value locally or pass it to client.setValue(value), then submit:
try {
await client.answer(value);
} catch (error) {
// The same error is also available as client.getState().error.
showError(error.message);
}Pollaris returns only the current question. After an answer, the server validates it and resolves the next graph edge. Do not attempt to calculate branching in the browser.
State
client.getState() and subscription callbacks return:
| Field | Meaning |
|---|---|
status | idle, loading, ready, submitting, complete, closed, or error |
form | Public title, description, branding, and close/duplicate settings |
responseId | Current public response session ID |
question | Current question, or null |
value | Value most recently passed to setValue |
progress | Approximate value from 0 to 1 |
completion | Completion message, redirect, and optional quiz score |
error | A PollarisError, or null |
Supported question types are yesno, single, multi, rating, nps, text, star, contact, date, and time. Treat question.config as type-specific configuration.
Lifecycle events
const offReady = client.on('ready', ({ form }) => {});
const offQuestion = client.on('question', ({ key, progress }) => {});
const offComplete = client.on('complete', ({ responseId }) => {});
const offError = client.on('error', ({ code, retryable }) => {});These events intentionally omit answer values. Avoid copying answer data into analytics, console logs, or error-reporting metadata.
Errors and retry
Stable public codes include:
FORM_NOT_FOUND FORM_CLOSED ORIGIN_NOT_ALLOWED
VALIDATION_FAILED RATE_LIMITED SESSION_EXPIRED
NETWORK_ERROR SERVER_ERROR PROTOCOL_ERRORCheck error.retryable. retry() is available only when the client has a retryable operation. A validation error should be shown beside the relevant control instead of retried unchanged.
Resume, reset, and cleanup
The default client storage remembers an in-progress response and duplicate-response status. This is a user-experience feature, not an anti-abuse security boundary.
await client.reset(); // clear this form's saved local state
unsubscribe();
client.destroy(); // abort requests and remove listenersUse storage: null when persistence is undesirable, or provide a StorageLike object with getItem, setItem, and removeItem.