Skip to main content
Once the script loads, the widget exposes one global function:
Everything here is optional — the widget works fully with just the script tag.

Identify your users

Tell Produck who’s giving feedback so each submission ties back to a real user instead of being anonymous. Call identify once you know who they are — typically right after login.
All three fields are optional — send whatever you have.

Pass a login token (JWT)

Already holding the user’s session token? Hand it over and we read the standard sub / email / name claims for you — one line for Supabase, Auth0, Clerk, Firebase, or Cognito:
We only read the claims (the signature is not verified), so this labels feedback inside your own project — it is not an authentication check.

Server-rendered pages (no JavaScript)

If your backend already knows the user at render time, set data-user-* on the script tag and skip identify entirely. data-user-jwt works the same way if you’d rather pass a token:

Clear identity on logout

If you need to identify a user before the script finishes loading, queue the call first. The widget replays anything queued the moment it boots, so nothing is lost:

Open the widget from your own UI

Wire up your own “Give feedback” button with trigger:

Fire a campaign trigger

Each trigger on a campaign’s Triggers tab has an id. When the moment it describes happens on your page, fire it, and the widget offers the visitor that campaign’s interview:
A server-rendered page can fire one with no script at all. Render a hidden element carrying the trigger id, and the widget fires it once it boots, or as soon as the element is added to the page later, so fragments swapped in by HTMX or Turbo fire too:
The offer says the trigger’s What should the trigger say to the end user? text, or “Got a minute to tell us how that went?” when it has none. A trigger fires at most once per browser tab session, so repeated calls, re-renders and later page loads in the same tab show no second offer. A fire shows nothing, and says why on the console, when the trigger is paused or its campaign isn’t live, a recording is already in progress, or the browser cannot record the screen. Accepting the offer starts the recording, and the interview is attributed to the trigger. Only the recording that accepted offer starts is attributed: a recording opened later from the Produck button, trigger or the shortcut carries no trigger, even after a declined offer. The offer shows even when the page hides the Produck button with data-launcher="none". It then sits on its own in the bottom-right corner, and once it’s declined or the recording is done the page shows nothing again.

Listen for events

React when a user submits or dismisses the widget — for example, to show a thank-you toast.

Scripted acts

A scripted flow can type into, select and click elements on your page as the visitor answers. Mark each element the script names with data-produck-target="<name>" — a text field receives, when the visitor’s answer commits, the value the script judged from their words — never the raw transcription — typed through its own value setter followed by a bubbling input event, so a React- or Vue-controlled input sees the change; anything else is clicked. A script can also signal a marked element instead of clicking it: the widget dispatches a bubbling produck:signal event on the element with the signal’s name in detail.name, draws the done ring, and leaves what the signal does to your page. The widget reads the marked targets at every answer and names what it read in its next acknowledgement. The widget draws a focus ring on a target a line asks the visitor to use themselves, and a done ring on every target it fills or clicks. A target missing from the page is reported on the console and the script carries on.

Bring your own UI

The widget injects two surfaces you can take over. Each is an ownership setting, not a boolean, so it stays meaningful as more options arrive: Set none on a surface to suppress the built-in version and render your own. Turning a surface off never removes capability — trigger, fire, submit, and submit_error keep working. The droplet is additionally suppressed where the browser cannot record a tab (no display capture, as on mobile browsers); trigger deliberately still works there, so a custom launcher keeps its contract.
Set the same thing with no JavaScript using data-* attributes on the script tag:
To call config before the script finishes loading, queue it with the same stub shown above for identify — the widget replays it on boot.

Keep your own Capture Handle

Before a tab recording the SDK tags the page with its own Capture Handle, so it can tell a capture of this very page from a capture of another tab. A page that already sets a handle for a companion capturer — a conferencing app, say — keeps it by declaring it with data-capture-handle. The SDK then reads that handle and leaves the page’s Capture Handle configuration alone. List your own origin in the handle’s permittedOrigins, because the SDK opens its capture from your page.
The keyboard shortcut (⌘/Ctrl + Shift + ?) still opens the widget when launcher is none — it’s an invisible trigger, not a visible surface. Rebind it with data-shortcut, or unbind it with data-shortcut="off", so your launcher is genuinely the only way in — see Keyboard shortcut.

Clean up on logout

Call destroy to remove the widget when a user’s session ends. On shared devices, this keeps the next user from seeing the previous one’s identity.