fdback.iofdback.io

Embedded Widget

Embed a feedback widget directly in your application

The embedded widget gives you a floating launcher button that opens an in-app panel (rendered in an iframe) for collecting feedback. It supports both anonymous and authenticated users.

Widget in full mode

Installation

Choose one:

# Bundlers (recommended)
npm install @fdback.io/sdk
# or
pnpm add @fdback.io/sdk
# or
yarn add @fdback.io/sdk
<!-- Script tag / CDN -->
<script src="https://unpkg.com/@fdback.io/sdk"></script>

The script tag build exposes a global FdbackSDK. With a bundler you import widget from @fdback.io/sdk.

Quick Start (Anonymous)

Use this when you want anyone to post/vote without logging in (and your workspace settings allow it).

import { widget } from "@fdback.io/sdk";

widget.init({
  workspaceId: "your-workspace-id",
});

Or with a script tag:

<script src="https://unpkg.com/@fdback.io/sdk"></script>
<script>
  FdbackSDK.widget.init({ workspaceId: "your-workspace-id" });
</script>

Quick Start (Authenticated)

Set signEndpoint to a server endpoint that returns HMAC-signed user data.

import { widget } from "@fdback.io/sdk";

widget.init({
  workspaceId: "your-workspace-id",
  signEndpoint: "/api/fdback/sign",
  onAuthenticated: (user) => {
    console.log("User authenticated:", user.email);
  },
});

The widget calls your signEndpoint with POST + credentials: "include" (cookies). If the endpoint returns a non-200 status (not logged in), the widget continues in anonymous mode.

Creating a signEndpoint (Next.js)

If you’re on Next.js App Router, use the server-only helper:

// app/api/fdback/sign/route.ts
import { createSignHandler } from "@fdback.io/sdk/next";

export const POST = createSignHandler({
  workspaceId: process.env.FDBACK_WORKSPACE_ID!,
  workspaceSecret: process.env.FDBACK_WORKSPACE_SECRET!,
});

For auth-aware examples, see Next.js Integration.

Configuration

These are the widget options (TypeScript):

interface WidgetConfig {
  workspaceId: string;

  baseUrl?: string; // Default: "https://app.fdback.io"

  mode?: "simple" | "full"; // Default: "full"
  position?: "bottom-right" | "bottom-left" | "top-right" | "top-left";
  tabs?: ("feedback" | "roadmap" | "changelog")[];
  primaryColor?: string;
  launcherIcon?: string; // Image URL, inline SVG, or HTML string
  theme?: "auto" | "dark" | "light"; // Default: "auto"
  width?: number; // Default: 440, Min: 320, Max: 600
  height?: number; // Default: 680, Min: 400, Max: 900

  signEndpoint?: string;
  user?: { email: string; name?: string };

  onOpen?: () => void;
  onClose?: () => void;
  onFeedbackSubmitted?: (feedback: { id: string }) => void;
  onTabChange?: (tab: string) => void;
  onAuthenticated?: (user: { id: string; email: string; name?: string | null }) => void;
}

Configuration Options

OptionTypeDefaultDescription
workspaceIdstringRequiredYour workspace ID
baseUrlstring"https://app.fdback.io"Widget iframe origin (useful for staging/self-hosting)
mode"simple" | "full""full"simple shows only the feedback form; full enables tabs
position"bottom-right" | "bottom-left" | "top-right" | "top-left""bottom-right"Launcher button position
tabs("feedback" | "roadmap" | "changelog")[]All tabsTabs to show (only used when mode: "full")
primaryColorstring-Hex color (e.g. #6366f1)
launcherIconstring-Custom launcher icon: image URL, inline SVG, or HTML string
theme"auto" | "dark" | "light""auto"Widget color theme
widthnumber440Widget width in pixels (min: 320, max: 600)
heightnumber680Widget height in pixels (min: 400, max: 900)
signEndpointstring-Your server endpoint for authentication (HMAC-signed)
user{ email: string; name?: string }-Optional user info (does not authenticate users)
onOpen() => void-Called when the widget opens
onClose() => void-Called when the widget closes
onFeedbackSubmitted(feedback: { id: string }) => void-Called after a feedback post is created
onTabChange(tab: string) => void-Called when the active tab changes
onAuthenticated(user) => void-Called after the widget logs a user in via signEndpoint

Widget Methods

widget.init() returns an instance for programmatic control:

import { widget } from "@fdback.io/sdk";

const instance = widget.init(config);

// Control
instance.open();                  // Open the widget panel
instance.close();                 // Close the widget panel
instance.toggle();                // Toggle open/closed state
instance.destroy();               // Remove widget from page

// State
instance.isOpen;                  // boolean - current state

// Navigation (full mode only)
instance.setTab("roadmap");       // Switch to a specific tab

// User management
instance.setUser({ email: "...", name: "..." });

The widget automatically closes when clicking outside of it or clicking the launcher button again.

Authentication Flow

When signEndpoint is configured, the widget authenticates users automatically:

┌─────────────────────────────────────────────────────────────┐
│  YOUR APP (acme.com)                                        │
│  1. widget.init({ signEndpoint: '/api/fdback/sign' })       │
│  2. SDK calls signEndpoint with cookies (for session)       │
│  3. Your server returns HMAC-signed user data               │
└────────────────────────┬────────────────────────────────────┘
                         │ Signed data via postMessage

┌─────────────────────────────────────────────────────────────┐
│  FDBACK WIDGET IFRAME (app.fdback.io)                       │
│  4. Validates HMAC signature with workspace secret          │
│  5. Creates/updates user, sets session cookie               │
│  6. User is authenticated - feedback attributed to them     │
└─────────────────────────────────────────────────────────────┘

Widget Modes

The examples below use the bundler API (import { widget } from "@fdback.io/sdk"). If you're using the script tag build, replace widget with FdbackSDK.widget.

Full Mode (Default)

Shows tabs for Feedback, Roadmap, and Changelog:

// Assumes: import { widget } from "@fdback.io/sdk";
widget.init({
  workspaceId: "your-workspace-id",
  mode: "full",
  tabs: ["feedback", "roadmap", "changelog"],
});

You can customize which tabs are shown:

// Assumes: import { widget } from "@fdback.io/sdk";
widget.init({
  workspaceId: "your-workspace-id",
  mode: "full",
  tabs: ["feedback", "roadmap"], // Hide changelog
});

Simple Mode

Shows only the feedback form (no tabs) — ideal for focused feedback collection:

// Assumes: import { widget } from "@fdback.io/sdk";
widget.init({
  workspaceId: "your-workspace-id",
  mode: "simple",
});

Tab Views

Roadmap Tab - Shows your product roadmap with status columns:

Roadmap tab

Changelog Tab - Displays product updates and announcements:

Changelog tab

Event Callbacks

onOpen / onClose

// Assumes: import { widget } from "@fdback.io/sdk";
widget.init({
  workspaceId: "your-workspace-id",
  onOpen: () => {
    console.log("Widget opened");
    // Track analytics event
  },
  onClose: () => {
    console.log("Widget closed");
  },
});

onFeedbackSubmitted

// Assumes: import { widget } from "@fdback.io/sdk";
widget.init({
  workspaceId: "your-workspace-id",
  onFeedbackSubmitted: (feedback) => {
    console.log("Feedback submitted:", feedback.id);
    // Show thank you message, track conversion, etc.
  },
});

onAuthenticated

Called when a user is successfully authenticated via signEndpoint:

// Assumes: import { widget } from "@fdback.io/sdk";
widget.init({
  workspaceId: "your-workspace-id",
  signEndpoint: "/api/fdback/sign",
  onAuthenticated: (user) => {
    console.log("User authenticated:", user.email);
    // Update UI to show user is logged in
  },
});

onTabChange

Called when the user switches tabs (full mode only):

// Assumes: import { widget } from "@fdback.io/sdk";
widget.init({
  workspaceId: "your-workspace-id",
  mode: "full",
  onTabChange: (tab) => {
    console.log("Switched to tab:", tab);
    // Track which tabs users visit
  },
});

Common Patterns

Open a specific tab from your own button

import { widget } from "@fdback.io/sdk";

widget.init({ workspaceId: "your-workspace-id" });

document.getElementById("open-roadmap")?.addEventListener("click", () => {
  widget.open();
  widget.setTab("roadmap");
});

Update the user after login

import { widget } from "@fdback.io/sdk";

widget.init({ workspaceId: "your-workspace-id" });

// later (after your app logs a user in)
widget.setUser({ email: "user@example.com", name: "Jane" });

Theming

Customize the widget's primary color:

// Assumes: import { widget } from "@fdback.io/sdk";
widget.init({
  workspaceId: "your-workspace-id",
  primaryColor: "#6366f1", // Indigo
});

Custom Launcher Icon

Replace the default feedback icon with your own image, SVG, or HTML:

// Image URL
widget.init({
  workspaceId: "your-workspace-id",
  launcherIcon: "https://example.com/my-icon.png",
});

// Inline SVG
widget.init({
  workspaceId: "your-workspace-id",
  launcherIcon: '<svg viewBox="0 0 24 24" fill="white"><path d="M12 2L2 22h20L12 2z"/></svg>',
});

The icon is rendered inside a 56x56px circle button. Recommended icon size: 24x24px.

React users can also use showLauncher={false} and provide their own custom trigger button for full control.

Dark Mode

The widget supports three theme modes:

// Assumes: import { widget } from "@fdback.io/sdk";
widget.init({
  workspaceId: "your-workspace-id",
  theme: "auto", // "auto" | "dark" | "light"
});
ValueBehavior
autoFollows user's system preference (default)
darkAlways use dark theme
lightAlways use light theme

Security

HMAC-SHA256 Signing

User authentication uses HMAC-SHA256 signatures:

  • Workspace Secret: Stored server-side only, never exposed to clients
  • Timestamp: Included in signature to prevent replay attacks (5-minute window)
  • Signature Validation: fdback.io validates the signature before creating a session

Anonymous vs Authenticated

ModeHow it worksFeedback attribution
AnonymousNo signEndpointauthorId = null
AuthenticatedsignEndpoint returns valid signed dataauthorId = user.id

The workspace secret must never be exposed in client-side code. Always use a server endpoint to sign user data.

Workspace Settings

The widget respects your workspace's anonymous access settings, configured in General Settings:

Anonymous Posts

When Allow Anonymous Posts is disabled:

  • Users must be authenticated via signEndpoint to submit feedback
  • The "Submit Feedback" button is hidden for anonymous visitors
  • A message prompts users to sign in

When enabled, anyone can submit feedback without authentication.

Anonymous Voting

When Allow Anonymous Voting is disabled:

  • Only authenticated users can vote on feedback
  • Vote buttons are hidden for anonymous visitors (vote counts are still shown)

When enabled, anonymous visitors can vote on posts.

Configure these settings in your workspace's General Settings page under "Public Board Settings".

React Usage

If you’re using React/Next.js, you can use the React wrapper (@fdback.io/sdk/react) instead of manually calling widget.init():

"use client";
import { FdbackWidget } from "@fdback.io/sdk/react";

export function AppWidget() {
  return (
    <FdbackWidget
      workspaceId="your-workspace-id"
      signEndpoint="/api/fdback/sign"
      mode="full"
    />
  );
}

See React Integration for more React helpers.

Complete Example

<!DOCTYPE html>
<html>
<head>
  <title>Fdback.io Docs</title>
</head>
<body>
  <h1>Welcome to Fdback.io</h1>
  <button id="open-feedback">Open feedback</button>
  <button id="open-roadmap">Open roadmap</button>

  <script src="https://unpkg.com/@fdback.io/sdk"></script>
  <script>
    // Initialize widget with authentication
    const widget = FdbackSDK.widget.init({
      workspaceId: "your-workspace-id",
      mode: "full",
      position: "bottom-right",
      primaryColor: "#6366f1",
      signEndpoint: "/api/fdback/sign",

      onOpen: () => console.log("Widget opened"),
      onClose: () => console.log("Widget closed"),
      onFeedbackSubmitted: (fb) => console.log("Feedback:", fb.id),
      onAuthenticated: (user) => console.log("User:", user.email),
      onTabChange: (tab) => console.log("Tab:", tab),
    });

    // Programmatic control
    document.getElementById("open-feedback").addEventListener("click", () => {
      widget.open();
      widget.setTab("feedback");
    });

    document.getElementById("open-roadmap").addEventListener("click", () => {
      widget.open();
      widget.setTab("roadmap");
    });
  </script>
</body>
</html>

Troubleshooting

See Troubleshooting for common widget issues (auth, CORS, widget not appearing).

On this page