Modes
Every Roastnest widget runs in cloud mode or self-hosted mode. The choice decides where data goes, which props you must pass, and which props the compiler rejects.
The difference
In cloud mode, the widget talks to Roastnest. It fetches its own configuration and theme from your dashboard on mount, uploads screenshots, and posts submissions to the API. You pass a project ID and nothing else.
In self-hosted mode, the widget talks to nothing. It renders the same UI, captures the same screenshots, and then hands you the payload through a callback. Where it goes next is entirely your code.
Cloud mode
Set mode="cloud" and a projectId on the provider. This is the default — omitting mode entirely gives you cloud.
<RoastnestProvider mode="cloud" projectId="YOUR_PROJECT_ID">
<FeedbackWidget />
</RoastnestProvider>What the cloud gives you that self-hosted does not:
- Configuration edited in the dashboard and applied without a redeploy — copy, button placement, notifications and theme all load at runtime.
- Screenshot hosting, so you are not storing image blobs yourself.
- Triage in the dashboard: status, priority, tags, comments and assignment.
- Integrations with Jira, Slack and Discord.
- Referral links and codes issued and validated server-side.
Self-hosted mode
Set mode="self-hosted" and supply the handler for whichever widget you render. Nothing is sent to Roastnest, and no project ID is required.
Feedback handler
onFormSubmit receives the message, the optional email, and the captured screenshots as Blobs. Return true for success and false for failure — the widget shows the corresponding message to the user.
<RoastnestProvider mode="self-hosted">
<FeedbackWidget
mode="self-hosted"
onFormSubmit={async ({ email, message, screenshotBlobs }) => {
const body = new FormData();
body.append("email", email ?? "");
body.append("message", message);
screenshotBlobs.forEach(({ blob, type }) => body.append(type, blob));
const res = await fetch("/api/feedback", { method: "POST", body });
return res.ok; // the widget shows success or error based on this
}}
/>
</RoastnestProvider>Referral handler
onEvent fires for every referral event — a link copied, a share, a tracked conversion — with the visitor, session and device metadata already attached.
<ReferralWidget
mode="self-hosted"
referralLink="https://myapp.com/invite"
onEvent={async (payload) => {
await fetch("/api/referral-events", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
}}
/>Referral links must match your hostname
In self-hosted mode the domain of referralLink has to match window.location.hostname. If it does not, the widget logs an error and renders nothing rather than handing out links that cannot attribute. Cloud mode validates the domain on the dashboard instead, so the check does not apply there.
Mode-enforced typing
Passing mode directly to a widget — not just to the provider — narrows its props at compile time. That does two useful things: it makes the required callbacks non-optional in self-hosted mode, and it makes local configuration illegal in cloud mode, where the server is the source of truth.
// In cloud mode the server owns the configuration, so passing it
// locally is a type error rather than a silent override.
<FeedbackWidget mode="cloud" onFormSubmit={handler} />
// ~~~~~~~~~~~~
// Type '{ mode: "cloud"; onFormSubmit: FormSubmitHandler; }' is not
// assignable to type 'FeedbackWidgetProps'.In cloud mode only the state controls stay available — visible, defaultOpen and closeOnBackdropClick — because those are presentation concerns your app legitimately owns.
Which should I pick?
Choose self-hosted if feedback content is sensitive, if you are already storing user reports somewhere, or if you want zero third-party network calls from your app. You give up the dashboard and the integrations, and you take on storing screenshots.
Choose cloud if you want triage, integrations and runtime configuration without building them. This is what most teams start with, and switching later means changing one prop and adding a handler.