---
title: Triggers and events
description:
  Open the Feeblo widget with markup attributes or code, then subscribe to widget
  events in vanilla JS or React.
---

The widget opens two ways: a reader clicks an element carrying the
`data-feeblo-feedback` attribute, or your code calls `open()`. While the widget runs,
it emits five events your JavaScript can subscribe to.

## Opening the widget from markup

Add `data-feeblo-feedback` to any element:

```html
<button data-feeblo-feedback>Give feedback</button>
```

The SDK finds these elements at init and rescans the page every second, so buttons you
render later pick up the trigger shortly after they mount. Each element binds once.
A click calls `preventDefault()` and `stopPropagation()` first, which means an anchor
trigger won't navigate away.

### Trigger metadata

Any other attribute starting with `data-feeblo-` travels with the click:

```html
<button data-feeblo-feedback data-feeblo-board="roadmap" data-feeblo-plan="enterprise">
  Give feedback
</button>
```

`data-feeblo-board` opens that board instead of the default one (board selection needs
`feedback` as your landing module). Every other attribute becomes a metadata key: drop
the prefix and camelCase the rest, so `data-feeblo-plan` arrives as `plan`. The widget
records this metadata on posts submitted while it's set, and the `feedbackSubmitted`
event hands it back to you. That answers "which button sent us here?" after the fact.

## Opening the widget from code

**Vanilla JS**

`init` returns a chainable handle with `open`, `close`, and `openModule`:

```ts
import { Feeblo } from "@feeblo/sdk";

const widget = Feeblo.init("org_123");

widget.open();
widget.close();
widget.openModule("updates");
widget.isOpen(); // true or false
```

`openModule` takes `"feedback"` or `"updates"` and only switches to modules the current
config enables. In single-module modes there's nothing to switch to; in hub mode it
jumps between the modules you listed. Because methods chain,
`widget.setBoard("roadmap").open()` works as one statement. The same methods also hang
off the static `Feeblo` object and act on the current instance.

**React**

`useFeeblo` exposes `open`, `close`, and `openModule`, plus a reactive `isOpen` you can
render from:

```tsx
import { useFeeblo } from "@feeblo/sdk-react";

function FeedbackButton() {
  const feeblo = useFeeblo();

  return (
    <button onClick={() => feeblo.openModule("feedback")}>
      {feeblo.isOpen ? "Close feedback" : "Give feedback"}
    </button>
  );
}
```

## The five events

Event names are exact strings:

| Event               | Fires when                                  | `event.detail.data`                          |
| ------------------- | ------------------------------------------- | -------------------------------------------- |
| `widgetReady`       | The widget iframe finished loading          | `undefined`                                   |
| `widgetOpened`      | The widget opened                           | `{ module }`, when the widget reported it     |
| `widgetClosed`      | The widget closed                           | `undefined`                                   |
| `identityChanged`   | An identify call reached the widget         | The identity object, minus the token          |
| `feedbackSubmitted` | The reader submitted a post                 | A `{ boardId, boardName, title, metadata? }` object |

Under the hood, each event is a `CustomEvent` dispatched on `window` whose `detail`
carries `{ data, type, namespace }`, with `namespace` always `"feeblo"`. You can listen
with `window.addEventListener` directly; the helpers below add typing and cleanup.

## Listening in vanilla JS

`Feeblo.on` subscribes and returns an unsubscribe function:

```ts
const off = Feeblo.on("feedbackSubmitted", (event) => {
  const post = event.detail.data;
  analytics.track("feedback_submitted", {
    board: post?.boardName,
    title: post?.title,
  });
});

// Stop listening when you no longer care.
off();
```

TypeScript narrows `event.detail.data` from the event name, so `post` is typed without
any casting on your side.

If you prefer symmetric add/remove, `Feeblo.off` takes the same arguments:

```ts
function onReady() {
  enableFeedbackButton();
}

Feeblo.on("widgetReady", onReady);
Feeblo.off("widgetReady", onReady);
```

Pass `"*"` as the event name to observe everything at once. `event.detail.type` tells
you which event fired:

```ts
Feeblo.on("*", (event) => {
  console.log("[feeblo]", event.detail.type, event.detail.data);
});
```

## Listening in React

The `useFeebloEvent` hook wraps the same subscription for component lifetimes:

```tsx
import { useFeebloEvent } from "@feeblo/sdk-react";

export function FeedbackAnalytics() {
  useFeebloEvent("feedbackSubmitted", (event) => {
    analytics.track("feedback_submitted", event.detail.data);
  });

  return null;
}
```

How it behaves:

- The subscription lasts as long as the component does; unmounting removes it.
- Pass `"*"` to observe every event.
- Write the handler inline. The hook reads it through a ref, so a fresh closure on each
  render never triggers a resubscribe, and every invocation reaches the latest handler.
- The hook talks to the core SDK directly rather than the provider context, so it works
  outside `<FeebloProvider>` too.

:::tip[Watch events while debugging]
Pass `debug: true` to `init` (or set the `debug` prop on `FeebloProvider`) and the SDK
logs every trigger binding and event to the console. See
[Configuration](/developers/configuration).
:::

## Widget events vs webhooks

Widget events live in the browser of whoever typed the feedback. Close the tab and
they're gone, and your server hears nothing about posts other people submit. To react on
the backend, subscribe to [webhooks](/developers/webhooks) instead: Feeblo delivers
`feedback.post.created` and `feedback.post.status_changed` over HTTPS regardless of who
is online.

## Next steps

<CardGroup cols={2}>
  <Card title="Configuration" href="/developers/configuration">
    Modes, module lists, placement, and the debug flag used above.
  </Card>
  <Card title="Webhooks" href="/developers/webhooks">
    Server-side delivery of post creation and status changes.
  </Card>
</CardGroup>
