# Public feedback widget

> Drop-in JavaScript widget to capture feedback and NPS scores from your own product directly into ProductOS, keyed by origin, no API token in the browser.

Source: https://productos.ca/documentation/api/v1/widget (last reviewed 2026-09-22)

The ProductOS feedback widget is a small JavaScript snippet you drop into your own product to let users send feedback or an NPS score directly into a ProductOS workspace. No API token in the browser, no CORS setup, one script tag.

Want your own UI instead, or to submit from a server or a mobile app? Use the [Feedback API](https://productos.ca/documentation/api/v1/feedback-api). It takes the same publishable keys.

## What it ships

- A floating launcher button (lower-right by default; draggable, and it snaps to the nearest edge of the viewport).
- A drawer with two modes: **Feedback** (a category chip plus text) and **Score** (NPS 0 to 10 with an optional comment).
- Keyboard shortcuts: `⌘/` or `Ctrl+/` toggles the drawer, `Esc` closes it, `⌘↵` or `Ctrl+↵` submits, and in Score mode `1` to `9`, `0` (10) and `-` (0) pick a score.
- Submissions go to `POST /api/v1/public/widget/feedback`, authenticated by a workspace **publishable key** and checked against the key's allowed origins.

## Issuing a publishable key

1. **Settings → Widget & API → Publishable keys → New key.**
2. Enter:
   - **Name**: for your own records, e.g. "Marketing site".
   - **Allowed origins**: comma-separated hosts, e.g. `acme.com, *.acme.com, localhost:3000`. A host without a port matches any port; `*.acme.com` matches subdomains; `*` allows any origin (not recommended for production).
3. Copy the `pk_live_*` key. It's shown once. Unlike API tokens, it **is safe to ship in the browser**: it can only submit feedback, and only from the origins you allowed.

## Installing

Drop this into your HTML:

```html title="index.html"
<script
  src="https://productos.ca/widget.js"
  data-key="pk_live_xxxxxxxxxxxxx"
  data-mode="feedback"
  async
></script>
```

The script mounts itself. `data-mode` picks the drawer that opens first: `feedback` or `nps`.

### Programmatic setup

Skip `data-key` and call `init` yourself to theme the widget or identify the user:

```html title="index.html"
<script src="https://productos.ca/widget.js" async></script>
<script>
  window.addEventListener("load", () => {
    ProductOSWidget.init({
      key: "pk_live_xxxxxxxxxxxxx",
      mode: "feedback", // or "nps"
      theme: {
        mode: "auto", // "light" | "dark" | "auto"
        accent: "#5e6ad2",
        radius: 8, // or "none" | "sm" | "md" | "lg"
      },
      launcher: { enabled: true, position: "br" }, // "br" | "bl" | "tr" | "tl"
    });

    // Attribute submissions to the signed-in user.
    ProductOSWidget.identify({ userId: "usr_8842", email: "jane@acme.com", name: "Jane Doe", company: "Acme" });
  });
</script>
```

The theme also accepts `background`, `foreground`, `muted`, `border` and `fontFamily`. Identified users appear on the submission in ProductOS, so your team can follow up. Call `ProductOSWidget.identify(null)` on sign-out.

### Opening from your own button

```javascript title="open the drawer"
ProductOSWidget.open();
ProductOSWidget.close();
```

Set `launcher: { enabled: false }` to hide the floating button and open the drawer only from your own "Send feedback" link. `ProductOSWidget.destroy()` removes the widget entirely.

## Building your own UI

Don't build on `/api/v1/public/widget/feedback`: it's the widget's own endpoint. It only accepts browser requests from allowed origins, returns no ID, and has no safe retries. Use the [Feedback API](https://productos.ca/documentation/api/v1/feedback-api) instead. It accepts the same fields, returns the submission's `FDBK-` reference, takes secret keys for server-side calls, and lets you read the status back.

## Attribution

The drawer footer shows "Powered by ProductOS". Whether it can be hidden is controlled by ProductOS on the server, per workspace; contact support to remove it.

## Privacy

- The widget calls ProductOS once when it loads (to read its configuration) and once per submission. Requests are sent without cookies.
- It keeps small pieces of state, such as where the launcher was dragged, in the page's localStorage.
- The submitter's IP is stored with the submission for abuse review. It isn't shown in the app.
- Identity passed to `identify` is visible to your team. Don't pass anything you wouldn't want a teammate with access to the workspace to see.

## Next

- [Feedback API](https://productos.ca/documentation/api/v1/feedback-api): the same intake, with your own code.
- [Feedback widget & API settings](https://productos.ca/documentation/pages/settings/widget-keys): managing keys and origins.
- [Authentication](https://productos.ca/documentation/api/v1/authentication): for non-widget API access.
