Triggers and events
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:
<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:
<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
init returns a chainable handle with open, close, and openModule:
import { Feeblo } from "@feeblo/sdk";
const widget = Feeblo.init("org_123");
widget.open();
widget.close();
widget.openModule("updates");
widget.isOpen(); // true or falseopenModule 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.
useFeeblo exposes open, close, and openModule, plus a reactive isOpen you can
render from:
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:
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:
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:
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:
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.
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 instead: Feeblo delivers
feedback.post.created and feedback.post.status_changed over HTTPS regardless of who
is online.