> ## Documentation Index
> Fetch the complete documentation index at: https://docs.buildbetter.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Feedback Widget

> Collect feedback directly from your users with an embeddable widget. Drop a single script tag onto any website to add a customizable feedback form.

## Quick Start

<Steps>
  <Step title="Get your credentials">
    Find your **API key** in **Settings > API Keys** and your **Feedback Source ID** on your feedback source's detail page.
  </Step>

  <Step title="Add the script tag">
    Paste this snippet into your HTML, replacing the placeholder values:

    ```html theme={null}
    <script
      src="https://app.buildbetter.app/widget/feedback-widget.js"
      data-api-key="YOUR_API_KEY"
      data-feedback-source-id="YOUR_SOURCE_ID"
    ></script>
    ```
  </Step>

  <Step title="You're live">
    A floating "Feedback" button appears in the bottom-right corner with a default text field. Users can click it to submit feedback directly to your BuildBetter feedback source.
  </Step>
</Steps>

### Data Attributes

When using the simple script tag, customize basic settings with `data-*` attributes:

| Attribute                 | Default           | Description                                                                             |
| ------------------------- | ----------------- | --------------------------------------------------------------------------------------- |
| `data-api-key`            | *(required)*      | Your organization API key                                                               |
| `data-feedback-source-id` | *(required)*      | The feedback source to submit to                                                        |
| `data-mode`               | `"floating"`      | Display mode: `floating`, `popover`, `inline`, or `slide-out`                           |
| `data-position`           | `"bottom-right"`  | Button position (floating mode): `bottom-right`, `bottom-left`, `top-right`, `top-left` |
| `data-button-text`        | `"Feedback"`      | Text on the trigger button                                                              |
| `data-title`              | `"Send Feedback"` | Title at the top of the form                                                            |

## Programmatic API

For full control, use the JavaScript API. The script exposes a global `BuildBetterFeedback` object.

```html theme={null}
<script src="https://app.buildbetter.app/widget/feedback-widget.js"></script>
<script>
  const widget = BuildBetterFeedback.create({
    apiKey: "YOUR_API_KEY",
    feedbackSourceId: "YOUR_SOURCE_ID",
    mode: "floating",
    position: "bottom-right",
    buttonText: "Send Feedback",
    title: "How can we improve?",
    fields: [
      { type: "emoji", name: "sentiment", label: "How do you feel?", required: true },
      { type: "textarea", name: "comments", label: "Tell us more", placeholder: "Your thoughts..." },
    ],
  });
</script>
```

### Instance Methods

`BuildBetterFeedback.create()` returns a widget instance with the following methods:

| Method             | Description                                                   |
| ------------------ | ------------------------------------------------------------- |
| `widget.open()`    | Open the widget (floating, popover, and slide-out modes only) |
| `widget.close()`   | Close the widget                                              |
| `widget.destroy()` | Remove the widget from the DOM entirely                       |

### Multiple Instances

You can create multiple widget instances on the same page. Each instance is fully isolated with its own Shadow DOM — styles never leak between widgets or affect your page.

```js theme={null}
// Quick reaction widget
BuildBetterFeedback.create({
  apiKey: "YOUR_API_KEY",
  feedbackSourceId: "SOURCE_1",
  mode: "floating",
  position: "bottom-right",
  fields: [
    { type: "emoji", name: "reaction", label: "Quick feedback", required: true },
  ],
});

// Detailed feedback form (inline)
BuildBetterFeedback.create({
  apiKey: "YOUR_API_KEY",
  feedbackSourceId: "SOURCE_2",
  mode: "inline",
  container: "#feedback-form",
  fields: [
    { type: "text", name: "title", label: "Subject", required: true },
    { type: "textarea", name: "details", label: "Description", required: true },
    { type: "select", name: "category", label: "Category", options: ["Bug", "Feature Request", "Question", "Other"] },
  ],
});
```

## Configuration Reference

<ParamField body="apiKey" type="string" required>
  Your organization API key (`bb_org_...`). Found in **Settings > API Keys**.
</ParamField>

<ParamField body="feedbackSourceId" type="string" required>
  The ID of the feedback source to submit records to.
</ParamField>

<ParamField body="mode" type="string" default="floating">
  Display mode. One of `floating`, `popover`, `inline`, or `slide-out`. See [Display Modes](#display-modes).
</ParamField>

<ParamField body="position" type="string" default="bottom-right">
  Trigger button position (floating mode only). One of `bottom-right`, `bottom-left`, `top-right`, `top-left`.
</ParamField>

<ParamField body="openDirection" type="string" default="above">
  Whether the form opens above or below the button (popover mode only). One of `above` or `below`.
</ParamField>

<ParamField body="container" type="string | HTMLElement">
  CSS selector or DOM element reference for inline and popover modes. Required when `mode` is `inline`. Optional for `popover` — if provided, the button renders inside the container; otherwise it appends to the body.
</ParamField>

<ParamField body="buttonText" type="string" default="Feedback">
  Text displayed on the trigger button (floating, popover, and slide-out modes).
</ParamField>

<ParamField body="title" type="string" default="Send Feedback">
  Title displayed at the top of the feedback form.
</ParamField>

<ParamField body="fields" type="FieldConfig[]">
  Array of field configurations. See [Field Types](#field-types). Defaults to a single required textarea.
</ParamField>

<ParamField body="theme" type="ThemeConfig">
  Visual customization options. See [Theming](#theming).
</ParamField>

<ParamField body="person" type="PersonConfig">
  Pre-fill user identity as hidden context. See [User Identity](#user-identity).
</ParamField>

<ParamField body="metadata" type="Record<string, string>">
  Hidden key-value pairs included with every submission. See [Metadata](#metadata).
</ParamField>

<ParamField body="hideBranding" type="boolean" default="false">
  Remove the "Powered by BuildBetter" footer from the widget.
</ParamField>

<ParamField body="onSubmit" type="(result: SubmissionResult) => void">
  Callback fired after a successful submission. Receives an object with `id` and `fields`.
</ParamField>

<ParamField body="onError" type="(error: Error) => void">
  Callback fired when a submission fails.
</ParamField>

<ParamField body="onOpen" type="() => void">
  Callback fired when the widget opens.
</ParamField>

<ParamField body="onClose" type="() => void">
  Callback fired when the widget closes.
</ParamField>

## Display Modes

<Tabs>
  <Tab title="Floating">
    A floating button fixed to a corner of the viewport. Clicking it opens a feedback card near the button.

    ```js theme={null}
    BuildBetterFeedback.create({
      apiKey: "YOUR_API_KEY",
      feedbackSourceId: "YOUR_SOURCE_ID",
      mode: "floating",
      position: "bottom-right", // or "bottom-left", "top-right", "top-left"
      buttonText: "Feedback",
    });
    ```
  </Tab>

  <Tab title="Popover">
    An inline button that opens the feedback form above or below it. Unlike floating mode, the button sits in the normal page flow wherever you place it — useful for embedding feedback into specific sections of your app.

    ```html theme={null}
    <div id="feedback-button"></div>
    <script>
      BuildBetterFeedback.create({
        apiKey: "YOUR_API_KEY",
        feedbackSourceId: "YOUR_SOURCE_ID",
        mode: "popover",
        container: "#feedback-button",
        openDirection: "above", // or "below"
        buttonText: "Give Feedback",
      });
    </script>
    ```

    The form card is centered over the button and opens with a subtle slide + scale animation.
  </Tab>

  <Tab title="Inline">
    Renders the form directly into a container element on your page. No trigger button — the form is always visible.

    ```html theme={null}
    <div id="my-feedback-form"></div>
    <script>
      BuildBetterFeedback.create({
        apiKey: "YOUR_API_KEY",
        feedbackSourceId: "YOUR_SOURCE_ID",
        mode: "inline",
        container: "#my-feedback-form",
      });
    </script>
    ```

    The `container` property accepts a CSS selector string or a DOM element reference. The inline form inherits the background color from your theme config.
  </Tab>

  <Tab title="Slide-Out">
    A tab fixed to the right edge of the viewport. Clicking it slides in a feedback panel.

    ```js theme={null}
    BuildBetterFeedback.create({
      apiKey: "YOUR_API_KEY",
      feedbackSourceId: "YOUR_SOURCE_ID",
      mode: "slide-out",
      buttonText: "Feedback",
    });
    ```
  </Tab>
</Tabs>

## Field Types

Configure which fields appear in your feedback form using the `fields` array. Each field requires a `type`, `name` (used as the key when submitting), and `label` (displayed to the user).

<AccordionGroup>
  <Accordion title="Text" icon="font">
    A single-line text input.

    ```js theme={null}
    {
      type: "text",
      name: "title",
      label: "Title",
      required: true,
      placeholder: "Enter a title..."
    }
    ```
  </Accordion>

  <Accordion title="Textarea" icon="align-left">
    A multi-line text area for longer responses.

    ```js theme={null}
    {
      type: "textarea",
      name: "details",
      label: "Details",
      placeholder: "Tell us more..."
    }
    ```
  </Accordion>

  <Accordion title="Emoji" icon="face-smile">
    A row of emoji buttons for quick sentiment feedback. The **text label** (not the emoji character) is submitted as the value, making it easy to filter and analyze.

    ```js theme={null}
    {
      type: "emoji",
      name: "sentiment",
      label: "How was your experience?",
      required: true
    }
    ```

    Default emojis: angry, sad, neutral, happy, love

    You can customize the emojis and their labels using `emojiOptions`:

    ```js theme={null}
    {
      type: "emoji",
      name: "sentiment",
      label: "Rate this feature",
      required: true,
      emojiOptions: [
        { emoji: "👎", label: "Not useful" },
        { emoji: "😐", label: "Okay" },
        { emoji: "👍", label: "Useful" },
        { emoji: "🔥", label: "Love it" },
      ]
    }
    ```
  </Accordion>

  <Accordion title="Star Rating" icon="star">
    A 1-5 star selector. Submits the numeric value as a string (e.g. `"4"`).

    ```js theme={null}
    {
      type: "star-rating",
      name: "rating",
      label: "Rate your experience",
      required: true
    }
    ```
  </Accordion>

  <Accordion title="Select (Dropdown)" icon="chevron-down">
    A dropdown with predefined options.

    ```js theme={null}
    {
      type: "select",
      name: "category",
      label: "Category",
      options: ["Bug Report", "Feature Request", "General Feedback", "Other"]
    }
    ```
  </Accordion>

  <Accordion title="NPS (Net Promoter Score)" icon="chart-bar">
    A 0-10 horizontal scale. Submits the numeric value as a string (e.g. `"8"`).

    ```js theme={null}
    {
      type: "nps",
      name: "score",
      label: "How likely are you to recommend us?",
      required: true
    }
    ```
  </Accordion>
</AccordionGroup>

### Field Properties

| Property       | Type            | Required | Description                                                                                                   |
| -------------- | --------------- | -------- | ------------------------------------------------------------------------------------------------------------- |
| `type`         | `string`        | Yes      | One of: `text`, `textarea`, `emoji`, `star-rating`, `select`, `nps`                                           |
| `name`         | `string`        | Yes      | Unique key for this field (used in the submitted data)                                                        |
| `label`        | `string`        | Yes      | Label displayed above the field                                                                               |
| `required`     | `boolean`       | No       | If `true`, the field must have a value before submitting                                                      |
| `placeholder`  | `string`        | No       | Placeholder text (`text` and `textarea` only)                                                                 |
| `options`      | `string[]`      | No       | Dropdown options (`select` only)                                                                              |
| `emojiOptions` | `EmojiOption[]` | No       | Custom emoji choices (`emoji` only). Each item has `emoji` (the character) and `label` (the submitted value). |
| `category`     | `string`        | No       | Override the API field category (advanced)                                                                    |

## Theming

Customize the widget's appearance to match your brand. The theme applies to all display modes including the inline form background.

```js theme={null}
BuildBetterFeedback.create({
  apiKey: "YOUR_API_KEY",
  feedbackSourceId: "YOUR_SOURCE_ID",
  theme: {
    primaryColor: "#6366f1",
    backgroundColor: "#ffffff",
    textColor: "#1f2937",
    borderRadius: 8,
    fontFamily: "Inter, sans-serif",
  },
});
```

| Property          | Type     | Default           | Description                                          |
| ----------------- | -------- | ----------------- | ---------------------------------------------------- |
| `primaryColor`    | `string` | `#6366f1`         | Accent color for buttons, selections, and highlights |
| `backgroundColor` | `string` | `#ffffff`         | Background color of the form card                    |
| `textColor`       | `string` | `#1f2937`         | Color of labels and body text                        |
| `borderRadius`    | `number` | `8`               | Corner radius in pixels                              |
| `fontFamily`      | `string` | System font stack | CSS `font-family` value                              |

<Tip>
  The widget uses theme-relative borders (derived from `textColor`) so input fields and controls remain visible on both light and dark backgrounds.
</Tip>

## User Identity

If your users are already logged in to your application, you can pass their identity to the widget so feedback is automatically linked to the person in BuildBetter — no need to ask them to fill in their name or email.

```js theme={null}
BuildBetterFeedback.create({
  apiKey: "YOUR_API_KEY",
  feedbackSourceId: "YOUR_SOURCE_ID",
  person: {
    email: currentUser.email,
    firstName: currentUser.firstName,
    lastName: currentUser.lastName,
  },
  fields: [
    { type: "emoji", name: "reaction", label: "How was this?", required: true },
    { type: "textarea", name: "feedback", label: "Comments" },
  ],
});
```

<Info>
  The `person` data is sent as hidden context — it is never displayed in the form. When an email is provided, the widget upserts a person record in BuildBetter and links the feedback submission to them. If the person already exists (matched by email), their record is reused. Name fields are only updated when provided — omitting them won't overwrite existing data.
</Info>

| Property    | Type     | Description                                        |
| ----------- | -------- | -------------------------------------------------- |
| `email`     | `string` | Email address (used to match or create the person) |
| `firstName` | `string` | First name                                         |
| `lastName`  | `string` | Last name                                          |

## Metadata

Pass hidden key-value data with every submission. Metadata fields are never shown in the form — they're submitted automatically alongside the user's responses and appear as additional columns in your feedback table.

This is useful for capturing page context, app state, or any other information that helps you understand where and when the feedback was given.

```js theme={null}
BuildBetterFeedback.create({
  apiKey: "YOUR_API_KEY",
  feedbackSourceId: "YOUR_SOURCE_ID",
  metadata: {
    page_url: window.location.href,
    app_version: "2.1.0",
    plan: "pro",
    doc_section: "getting-started",
  },
  fields: [
    { type: "emoji", name: "helpful", label: "Was this page helpful?", required: true },
    { type: "textarea", name: "feedback", label: "Any other thoughts?" },
  ],
});
```

Each key becomes a column in the feedback records table (e.g. `page_url` appears as "Page Url"). All metadata values are stored as strings with `category: "metadata"`.

<Info>
  **`person` vs `metadata`**: Use `person` for user identity (email, name) — it creates/links a person record in BuildBetter. Use `metadata` for contextual data about the submission (page URL, plan, app version) — it's stored as fields on the feedback record itself.
</Info>

## Event Callbacks

React to widget lifecycle events with callback functions.

```js theme={null}
const widget = BuildBetterFeedback.create({
  apiKey: "YOUR_API_KEY",
  feedbackSourceId: "YOUR_SOURCE_ID",

  onSubmit: (result) => {
    console.log("Feedback submitted:", result.id);
    // result.fields contains the submitted field values
  },

  onError: (error) => {
    console.error("Submission failed:", error.message);
  },

  onOpen: () => {
    console.log("Widget opened");
  },

  onClose: () => {
    console.log("Widget closed");
  },
});
```

## Examples

<AccordionGroup>
  <Accordion title="Customer Satisfaction Survey">
    A floating NPS survey with optional follow-up, linked to the logged-in user.

    ```js theme={null}
    BuildBetterFeedback.create({
      apiKey: "YOUR_API_KEY",
      feedbackSourceId: "YOUR_SOURCE_ID",
      mode: "floating",
      position: "bottom-right",
      title: "How are we doing?",
      fields: [
        { type: "nps", name: "nps_score", label: "How likely are you to recommend us?", required: true },
        { type: "textarea", name: "reason", label: "What's the main reason for your score?", placeholder: "Optional" },
      ],
      person: {
        email: currentUser.email,
        firstName: currentUser.name,
      },
      theme: {
        primaryColor: "#2563eb",
      },
    });
    ```
  </Accordion>

  <Accordion title="Docs Page Feedback (Popover)">
    A popover button embedded in your documentation pages that captures the current URL automatically.

    ```html theme={null}
    <div id="docs-feedback"></div>
    <script src="https://app.buildbetter.app/widget/feedback-widget.js"></script>
    <script>
      BuildBetterFeedback.create({
        apiKey: "YOUR_API_KEY",
        feedbackSourceId: "YOUR_SOURCE_ID",
        mode: "popover",
        container: "#docs-feedback",
        openDirection: "above",
        buttonText: "Was this helpful?",
        title: "Page Feedback",
        fields: [
          { type: "emoji", name: "helpful", label: "How helpful was this page?", required: true },
          { type: "textarea", name: "feedback", label: "How can we improve it?", placeholder: "Optional" },
        ],
        metadata: {
          page_url: window.location.href,
          page_title: document.title,
        },
      });
    </script>
    ```
  </Accordion>

  <Accordion title="Feature Request Form (Inline)">
    An inline form embedded in your page for collecting detailed feature requests.

    ```html theme={null}
    <h2>Request a Feature</h2>
    <div id="feature-request"></div>

    <script src="https://app.buildbetter.app/widget/feedback-widget.js"></script>
    <script>
      BuildBetterFeedback.create({
        apiKey: "YOUR_API_KEY",
        feedbackSourceId: "YOUR_SOURCE_ID",
        mode: "inline",
        container: "#feature-request",
        title: "Tell us what you need",
        fields: [
          { type: "text", name: "title", label: "Feature title", required: true, placeholder: "Brief description" },
          { type: "textarea", name: "description", label: "Details", required: true, placeholder: "Describe the feature and why it would be useful..." },
          { type: "select", name: "priority", label: "How important is this?", options: ["Nice to have", "Important", "Critical"] },
          { type: "select", name: "area", label: "Product area", options: ["Dashboard", "Reports", "Integrations", "API", "Other"] },
        ],
        person: {
          email: currentUser.email,
          firstName: currentUser.firstName,
          lastName: currentUser.lastName,
        },
      });
    </script>
    ```
  </Accordion>

  <Accordion title="Quick Reaction Widget">
    A minimal floating widget with custom emojis for page-level feedback.

    ```js theme={null}
    BuildBetterFeedback.create({
      apiKey: "YOUR_API_KEY",
      feedbackSourceId: "YOUR_SOURCE_ID",
      mode: "floating",
      position: "bottom-left",
      buttonText: "Rate this page",
      title: "Quick Feedback",
      fields: [
        {
          type: "emoji",
          name: "reaction",
          label: "Was this page helpful?",
          required: true,
          emojiOptions: [
            { emoji: "👎", label: "Not helpful" },
            { emoji: "😐", label: "Somewhat helpful" },
            { emoji: "👍", label: "Very helpful" },
          ],
        },
      ],
      theme: {
        primaryColor: "#10b981",
        borderRadius: 12,
      },
    });
    ```
  </Accordion>

  <Accordion title="Bug Report with Star Rating">
    A slide-out panel for structured bug reports with severity rating.

    ```js theme={null}
    BuildBetterFeedback.create({
      apiKey: "YOUR_API_KEY",
      feedbackSourceId: "YOUR_SOURCE_ID",
      mode: "slide-out",
      title: "Report an Issue",
      fields: [
        { type: "star-rating", name: "severity", label: "How severe is this issue?", required: true },
        { type: "text", name: "summary", label: "Summary", required: true, placeholder: "Brief description of the issue" },
        { type: "textarea", name: "steps", label: "Steps to reproduce", placeholder: "1. Go to...\n2. Click on...\n3. See error" },
        { type: "select", name: "browser", label: "Browser", options: ["Chrome", "Firefox", "Safari", "Edge", "Other"] },
      ],
      metadata: {
        page_url: window.location.href,
        user_agent: navigator.userAgent,
      },
    });
    ```
  </Accordion>

  <Accordion title="Dark Theme Widget">
    A fully dark-themed floating widget. The widget uses theme-relative borders so controls stay visible on dark backgrounds.

    ```js theme={null}
    BuildBetterFeedback.create({
      apiKey: "YOUR_API_KEY",
      feedbackSourceId: "YOUR_SOURCE_ID",
      mode: "floating",
      position: "bottom-right",
      title: "Feedback",
      fields: [
        { type: "emoji", name: "sentiment", label: "How do you feel?", required: true },
        { type: "textarea", name: "feedback", label: "Tell us more" },
      ],
      theme: {
        primaryColor: "#22c55e",
        backgroundColor: "#1a1a2e",
        textColor: "#e2e8f0",
        borderRadius: 12,
      },
    });
    ```
  </Accordion>
</AccordionGroup>

## Widget Builder

<Tip>
  You can build and preview your widget visually inside BuildBetter. Go to **Feedback > Widget Builder** to configure fields, display mode, and theme with a live interactive preview that runs the real widget, then copy the generated embed code.
</Tip>

## Technical Details

<CardGroup cols={2}>
  <Card title="Lightweight" icon="feather">
    \~12 KB gzipped. Single JS file, no CSS dependency, no external libraries needed on your page.
  </Card>

  <Card title="Style Isolation" icon="shield">
    Each widget instance renders inside a Shadow DOM, so its styles never conflict with your page.
  </Card>

  <Card title="Spam Prevention" icon="clock">
    Built-in 5-second cooldown between submissions. Each submission includes a unique ID for server-side deduplication.
  </Card>

  <Card title="Browser Support" icon="globe">
    Works in all modern browsers: Chrome, Firefox, Safari, and Edge. See [Browser Compatibility](#browser-compatibility) for details.
  </Card>
</CardGroup>

## Browser Compatibility

The widget uses Shadow DOM for style isolation, which requires a modern browser. Internet Explorer is **not** supported.

| Browser        | Minimum Version | Released |
| -------------- | --------------- | -------- |
| Chrome         | 53+             | 2016     |
| Edge           | 79+             | 2020     |
| Firefox        | 63+             | 2018     |
| Safari         | 10+             | 2016     |
| Safari iOS     | 10+             | 2016     |
| Chrome Android | 53+             | 2016     |

<Warning>
  Internet Explorer 11 and legacy Edge (pre-Chromium) are not supported. The widget will not render in these browsers.
</Warning>
