Install the widget
The default path is Copy prompt for your agent on Admin → Settings → Widget → Install. Paste it into the coding agent in the host app repo. The launcher appears in anonymous mode as soon as init runs. If the host already has login, the agent also identifies signed-in users.
Install with your coding agent
The prompt includes a short-lived pairing code. The agent redeems POST /api/widget/install-context and writes the signing secret to a server-only host env var (any name). The secret never goes in the chat. Redeeming also turns on Show on your website.
The agent wires identify only when the host already has a session or login. On a static or logged-out-only site it stops after init and leaves the secret in env.
Humans who are not using an agent can open Install without an agent on that page for the snippet. The signing secret is not an install step. Open it only for hand wiring or rotation. After the widget connects, the same page becomes a manage screen (status, visibility, add to another site, secret).
Tabs, colors, and launcher copy come from Admin → Settings → Widget.
Embed the snippet
Hand install is secondary. Prefer the agent prompt above.
- Go to Admin → Settings → Widget → Install
- Open Install without an agent, copy the snippet, and paste it before the closing
</body>tag - Turn on Show on your website if the agent did not already (also on Widget settings)
The snippet includes commented identify steps. Init the launcher first. Identify signed-in users with a backend-signed ssoToken when the host has login.
The snippet looks like this (your instance URL is pre-filled in admin):
<script>
(function(w,d){if(w.Quackback)return;w.Quackback=function(){
(w.Quackback.q=w.Quackback.q||[]).push(arguments)};
var s=d.createElement("script");s.async=true;
s.src="https://feedback.example.com/api/widget/sdk.js";
d.head.appendChild(s)})(window,document);
Quackback("init");
// Quackback("identify", { ssoToken });
// Quackback("logout");
</script>Replace https://feedback.example.com with your Quackback instance URL.
The snippet creates a command queue, then loads the SDK. Calls you make before the script loads replay automatically. Script-tag installs can omit instanceUrl because the SDK reads it from sdk.js.
Turn on Show on your website in Admin → Settings → Widget → Install (or Widget settings) or the launcher stays hidden even after the snippet loads.
Install with npm
npm install @quackback/widgetimport { Quackback } from "@quackback/widget";
Quackback.init({ instanceUrl: "https://feedback.example.com" });instanceUrl is required for the npm package. Vue, Svelte, Angular, and Solid can call Quackback the same way.
React
import { useQuackbackInit, useQuackback, useQuackbackEvent } from "@quackback/widget/react";
function App() {
useQuackbackInit({
instanceUrl: "https://feedback.example.com",
});
useQuackbackEvent("post:created", (post) => {
analytics.track("feedback_submitted", { postId: post.id });
});
return <Layout />;
}
function FeedbackButton() {
const qb = useQuackback();
return (
<button onClick={() => qb.open({ view: "new-post" })}>Feedback</button>
);
}No provider is required. The hooks wrap the singleton SDK.
Identify is optional and is not required for the launcher. If you later want signed-in attribution, fetch a backend-signed ssoToken, never a raw id or email. See Identify users.
Configure with init options
Script tag:
Quackback("init", {
placement: "left",
defaultBoard: "feature-requests",
locale: "fr",
});npm:
Quackback.init({
instanceUrl: "https://feedback.example.com",
placement: "left",
defaultBoard: "feature-requests",
locale: "fr",
});| Option | Type | Default | Description |
|---|---|---|---|
instanceUrl | string | - | Your Quackback URL. Required for npm. Baked into script-tag sdk.js. |
placement | "left" | "right" | "right" | Corner for the launcher and panel until admin config loads |
defaultBoard | string | - | Board slug to filter the widget. Omit to show all boards |
launcher | boolean | true | Set false to hide the default floating button |
locale | string | Auto-detected | "en", "de", "fr", "es", "ar", "ru", "pt-BR", "zh-CN", "zh-TW", "nl", or "pl" |
identity | object | - | Bundle identify into init. Production shape is { ssoToken }. Omit for anonymous |
Admin → Settings → Widget → Layout owns launcher position, label, and greeting. The admin position overrides placement after the widget loads its config.
When launcher is false, open the panel from your own UI:
Quackback.init({
instanceUrl: "https://feedback.example.com",
launcher: false,
});
document.getElementById("feedback-btn").addEventListener("click", () => {
Quackback.open();
});Script-tag calls use the same names: Quackback("open"), Quackback("identify", { ssoToken }).
SDK methods
| Method | Description |
|---|---|
Quackback.init(options) | Create the launcher and iframe. instanceUrl required for npm. |
Quackback.identify(identity) | Attribute activity to a signed-in user. Pass { ssoToken }. Do not call identify for anonymous visitors. |
Quackback.logout() | Clear identity. The widget stays visible in anonymous mode. |
Quackback.open(options?) | Open the panel. Optional deep-link payload (see below). |
Quackback.close() | Close the panel. |
Quackback.showLauncher() / hideLauncher() | Show or hide the floating button. |
Quackback.metadata(patch) | Attach session context to submitted feedback. Pass null to remove a key. |
Quackback.on(event, handler) | Subscribe to an event. Returns an unsubscribe function. |
Quackback.off(event, handler?) | Remove one handler, or all listeners for that event. |
Quackback.destroy() | Tear down widget state and DOM. |
Quackback.isOpen() | Whether the panel is visible. |
Quackback.getUser() | The current identified user, or null. |
Quackback.isIdentified() | true when a signed-in user is identified. |
identify
See Identify users for JWT claims and server examples.
const { ssoToken } = await fetch("/widget-token").then((r) => r.json());
Quackback.identify({ ssoToken });Calling identify again with a new token switches the active user.
open
Quackback.open();
Quackback.open({ view: "new-post", title: "Bug: page crashes on save", board: "bugs" });
Quackback.open({ view: "changelog" });
Quackback.open({ view: "help", query: "pricing" });
Quackback.open({ view: "chat" }); // Messages tab; `messages` also works
Quackback.open({ view: "tickets" });
Quackback.open({ postId: "post_01h..." });
Quackback.open({ articleId: "art_01h..." });| Option | Type | Description |
|---|---|---|
view | "home" | "new-post" | "changelog" | "help" | "chat" | "messages" | "tickets" | Which surface to open. Default is home. |
title | string | Pre-fill the post title with view: "new-post" |
board | string | Board slug to pre-select with view: "new-post" |
query | string | Help search query with view: "help" |
body | string | Pre-fill the post body with view: "new-post" |
postId | string | Deep-link to a post |
articleId | string | Deep-link to a help article |
entryId | string | Deep-link to a changelog entry |
view, title, and board are handled by the iframe today. body, query, postId, articleId, and entryId pass through and render as iframe support lands.
logout, close, destroy
Quackback.logout();
Quackback.close();
Quackback.destroy();Call logout from your logout handler. Call destroy in SPAs when you unmount the host page.
Single-page apps
Initialize once in a layout. Clean up on unmount. Skip the second effect unless you copied the signing secret from Admin → Settings → Widget → Install.
import { useEffect } from "react";
import { Quackback } from "@quackback/widget";
import { useAuth } from "@/hooks/use-auth";
export function FeedbackWidget() {
const { user } = useAuth();
useEffect(() => {
Quackback.init({ instanceUrl: "https://feedback.example.com" });
return () => Quackback.destroy();
}, []);
useEffect(() => {
if (!user) return;
fetch("/widget-token")
.then((res) => res.json())
.then(({ ssoToken }) => {
Quackback.identify({ ssoToken });
});
}, [user]);
return null;
}The SDK queues identify until the iframe is ready. You do not need to wait for a ready event.
Next steps
- Identify users: Sign
ssoTokenon your server - Listen to widget events: React to posts, votes, and comments
- Widget overview: Surfaces, admin editor, and locales