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.

Cloud setup
<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.

Self-hosted feedback
<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.

Self-hosted referrals
<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.

TypeScript
// 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.