Install the widget

Add the widget to your site with your coding agent, a script tag, or npm (React hooks included), then control it with init options and SDK methods like open, identify, and logout.

JM
James Morton
Written By James MortonLast updated 1 day ago

This article shows how to add the widget to your site or app, either by handing a prompt to your coding agent or by installing it yourself with a script tag or npm, and lists the SDK options and methods.

  1. Go to Settings > Widget > Install.
  2. Under 1. Copy the prompt for your agent, click Copy install prompt.
  3. Paste the prompt into the coding agent working in your app's repository (the host app).
  4. After the agent finishes, open a page on your site (localhost is fine). The install page shows when the widget connects.

The prompt includes a short-lived pairing code. The agent redeems it to fetch your workspace's signing secret and writes the secret to a server-only environment variable in your app (any name works). The secret never appears in the chat. Redeeming the code also turns on Show on your website.

If your app already has sign-in, the agent also identifies signed-in users. On a static or logged-out site, it stops after init, and the widget runs for anonymous visitors.

After the widget connects, the Install page becomes a manage screen with the connection status, visibility, Add to another site, and the signing secret.

Install by hand

Script tag

  1. Go to Settings > Widget > Install and open Install without an agent.
  2. Click Copy snippet and paste it before the closing </body> tag on your site.
  3. Make sure Show on your website is on in Settings > Widget. If it is off, the launcher stays hidden even after the snippet loads.

The snippet looks like this (your workspace URL is filled in for you):

<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>

The snippet creates a command queue and then loads the SDK. Calls you make before the script loads run automatically once it does. Script-tag installs do not need instanceUrl, because the SDK reads it from sdk.js.

npm

npm install @quackback/widget
import { Quackback } from "@quackback/widget";

Quackback.init({ instanceUrl: "https://feedback.example.com" });

instanceUrl is required with the npm package. Vue, Svelte, Angular, and Solid apps 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 needed. The hooks wrap the single SDK instance.

Tip: Identifying users is optional, and the launcher works without it. For signed-in attribution, fetch a token signed by your server, never a raw id or email. See Identify users.

Init options

Quackback("init", {
  placement: "left",
  defaultBoard: "feature-requests",
  locale: "fr",
});

Option

Type

Default

Description

instanceUrl

string

-

Your Quackback URL. Required for npm. Built into the script-tag sdk.js.

placement

"left" | "right"

"right"

Corner for the launcher and panel until the admin settings load

defaultBoard

string

-

Board slug to filter the Feedback tab. 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

-

Identify at init with { ssoToken }. Omit for anonymous visitors.

Note: Settings > Widget > Layout controls the launcher position, label, and greeting. The admin position overrides placement once the widget loads its settings.

Use your own button

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 panel. instanceUrl is required for npm.

Quackback.identify({ ssoToken })

Attribute activity to a signed-in user. Do not call it for anonymous visitors.

Quackback.logout()

Clear the identity. The widget stays visible for anonymous use.

Quackback.open(options?)

Open the panel, optionally to a specific view (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 every handler for that event

Quackback.destroy()

Remove the widget and its state from the page

Quackback.isOpen()

Whether the panel is open

Quackback.getUser()

The identified user, or null

Quackback.isIdentified()

true when a signed-in user is identified

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" });
Quackback.open({ view: "tickets" });
Quackback.open({ postId: "post_01h..." });
Quackback.open({ articleId: "article_01h..." });

Option

Description

view

"home", "new-post", "changelog", "help", "chat" (also "messages"), or "tickets". Default is home.

title

Prefill the post title with view: "new-post"

body

Prefill the post body with view: "new-post"

board

Board slug to preselect with view: "new-post"

query

Prefill the help search with view: "help"

entryId

Open one changelog entry with view: "changelog"

postId

Open a specific post

articleId

Open a specific help article (id or slug)

postId and articleId take priority over view. If the target's tab is turned off, or the board is not visible to the visitor, the panel opens without switching to it.

logout, close, destroy

Quackback.logout();
Quackback.close();
Quackback.destroy();

Call logout from your app's logout handler. Call destroy in single-page apps when you unmount the page that hosts the widget.

Single-page apps

Initialize once in a layout and clean up on unmount. The second effect is only needed if your app has sign-in and your server signs widget tokens.

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 panel is ready, so you do not need to wait for a ready event.

Was this helpful?

Your feedback shapes what we write next.