Skip to main content

Widget

The embeddable feedback widget lets your users submit feedback, bug reports, and questions directly from your app. It renders in a Shadow DOM for full style isolation and is under 30KB gzipped.

Script tag integration

Add a single script tag before </body>. Replace the API key with yours from the dashboard.

HTML
<script
  src="https://userdispatch.com/widget.js"
  data-api-key="pk_your-api-key"
  defer
></script>

The widget renders a floating feedback button. Users can submit feedback, bug reports (with screenshots via drag-and-drop), and questions.

If you prefer not to use the floating widget, you can embed a simple link or button on your site that opens a hosted feedback page:

HTML
<a href="https://userdispatch.com/submit?key=pk_your-api-key">
  Give Feedback
</a>

This is useful for email signatures, documentation footers, or anywhere you want a lightweight feedback entry point without loading the widget script.

Framework guides

Use the Script component in your root layout:

app/layout.tsx
import Script from "next/script";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <Script
          src="https://userdispatch.com/widget.js"
          data-api-key="pk_your-api-key"
          strategy="afterInteractive"
        />
      </body>
    </html>
  );
}

Configuration options

Configure the widget via data attributes on the script tag:

AttributeRequiredDefaultDescription
data-api-keyYes—Your app's API key (starts with pk_)
data-positionNo"br"Trigger button position: "br" (bottom-right), "bl", "tr", "tl"
data-trigger-labelNo"Feedback"Text on the trigger button
data-collect-emailNo"true"Show email field. Set to "false" to hide.
data-collect-nameNo"false"Show name field. Set to "true" to enable.
data-enable-logsNo"false"Enable built-in console log collector. Set to "true" to enable. Requires app-level log upload setting.
data-api-urlNo(auto-detected)Override the API base URL. Detected automatically from the script src.

JavaScript API

For programmatic control, use the global UserDispatch object:

JavaScript
// Initialize with custom config
const widget = UserDispatch.init({
  apiKey: "pk_your-api-key",
  position: "br",
  triggerLabel: "Send Feedback",
  darkMode: true,
  types: ["general", "support", "bug"],
  collectEmail: true,
  collectName: false,
  metadata: { plan: "pro", version: "2.1.0" },
  userContext: { id: "user_123", name: "Jane" },
  onSubmit: (result) => console.log("Submitted:", result),
  onError: (error) => console.error("Error:", error),
});

// Later: remove the widget
widget.destroy();

Programmatic submission (no UI)

JavaScript
await UserDispatch.submit({
  apiKey: "pk_your-api-key",
  type: "bug",
  email: "user@example.com",
  subject: "Page crash on checkout",
  message: "The page freezes when clicking Pay",
  metadata: { page: "/checkout", cartId: "abc123" },
});

SDK (@userdispatch/sdk)

The TypeScript SDK provides a clean API for server-side or programmatic use:

Terminal
npm install @userdispatch/sdk
TypeScript
import { UserDispatch } from "@userdispatch/sdk";

const ud = new UserDispatch({ apiKey: "pk_your-api-key" });

// Submit feedback
await ud.submit({
  type: "feedback",
  message: "Love the new dashboard!",
  rating: 5,
  email: "user@example.com",
});

// Submit an automated log
await ud.submitLog({
  message: "Unhandled promise rejection in /api/checkout",
  metadata: {
    stack: "Error: timeout at fetch.js:42",
    severity: "error",
  },
});

Submission types

Widget typeDB typeDescription
generalfeedbackGeneral feedback with optional star rating
supportquestionSupport questions
bugbug_reportBug reports with subject line and file attachments
—automated_logAutomated logs via SDK (no widget equivalent)

Allowed origins

Each app can restrict which origins may send feedback. Set the list under App settings → Allowed origins, or with the allowed_origins field of the apps MCP tool and the admin API. An empty list accepts every origin. Once the list has an entry, a request whose Origin header is not on it is refused with 403 Origin not allowed.

An origin is scheme://host[:port]. Paths, trailing slashes and upper case are normalized away, and wildcards are not supported.

Native apps and web views

A web view sends its own origin, not your website's. If the widget runs inside a native shell, add that shell's origin as well as your website:

ShellOrigin to allow
Capacitor, iOScapacitor://localhost
Capacitor, Androidhttps://localhost (the default androidScheme)
Ionic or Cordova, iOSionic://localhost
Cordova, Androidhttp://localhost (older builds: http://localhost:8080)
React Native WebViewThe origin of the page it loads, usually your https:// site. A WebView given inline HTML sends null: load a real URL instead.
ElectronA page loaded from file:// sends null and cannot be allowed. Serve it from a custom scheme or https://localhost.

If your Capacitor config changes server.iosScheme, server.androidScheme or server.hostname, allow the resulting origin instead (for example capacitor://myapp). Native HTTP clients, including React Native's own fetch, send no Origin header and are refused whenever a list exists: submit from a server, or leave the list empty.

Diagnosing a 403

  1. The widget shows the refused origin in its error message, and the 403 response body carries { "code": "origin_not_allowed", "origin": "..." }.
  2. The app's settings page lists origins refused in the last 7 days with an Allow this origin button. Counts are a lower bound.
  3. No origin shown? The request carried no Origin header, which happens with server-side calls. Leave the list empty, or send submissions from an allowed browser origin.

Ramble needs an https origin

Turning Ramble on adds the Ramble pilot origin to the list when it is missing, which makes the list a restriction. If the list is empty today, the dashboard asks you to confirm once before it does that, so add your native shells first. While Ramble is on, the list must keep that origin; other origins and schemes can sit alongside it. Turning Ramble off removes the origin again, but only if Ramble added it. Origins you entered yourself are never removed.

File uploads

Users can attach files by dragging and dropping anywhere on the widget modal. Supported formats include images (PNG, JPG, GIF, WebP), documents (PDF), and text files. Files are attached to the submission and accessible from the dashboard.

Browser metadata

Every submission automatically captures:

  • User agent string
  • Viewport dimensions
  • Page URL
  • Operating system
  • Screen resolution
  • Custom metadata (via data-* attributes or JS API)
Widget — UserDispatch Docs