Framework guide · React

Add a feature request board to React

This is the implementation reference. For architecture decisions (where the board belongs in your IA, private vs public strategy, storage choices), see the architectural guide.

Install

pnpm add @bootstrapware/feedback
# or
npm install @bootstrapware/feedback

Import the stylesheet once, near the feature or in a layout that wraps it. Forgetting styles.css is the most common "it looks broken" bug.

import { Feedback, createLocalAdapter } from "@bootstrapware/feedback";
import "@bootstrapware/feedback/styles.css";

The component is a client component

<Feedback /> runs in the browser. In Next.js App Router, wrap it in a "use client" file or a client component boundary.

Component props

PropTypePurpose
user{ id, name?, email?, avatarUrl? }Required. Your app asserts identity; Bootstrapware does not auth end users.
adapterFeedbackAdapterBYO or local adapter. Pass this to own posts. Takes precedence over Hosted.
boardIdstringHosted board ID. Required together with publishableKey for Hosted mode.
publishableKeystringPublishable key (bsw_*_pub_). Safe in client code. Required for Hosted mode.
authorTokenstring?Short-lived signed assertion minted server-side. Required when board has requireAuthorToken: true. See identity.
apiBaseUrlstring?Override API origin. Defaults to https://feedback.bootstrapware.co.

Mode resolution order

  1. Explicit adapter prop → that adapter is used (BYO or local).
  2. boardId + publishableKey → Hosted adapter is constructed automatically.
  3. Otherwise a local in-memory adapter is used (browser-only, no account needed).

Local / demo mode

import { Feedback, createLocalAdapter } from "@bootstrapware/feedback";
import "@bootstrapware/feedback/styles.css";

export function IdeasBoard({ user }: { user: { id: string; name?: string } }) {
  return (
    <Feedback
      user={user}
      adapter={createLocalAdapter({ storageKey: "product-ideas" })}
    />
  );
}

Data is stored in localStorage. Free forever. Use it to validate placement before committing to BYO or Hosted storage.

Hosted mode

<Feedback
  boardId="brd_..."
  publishableKey="bsw_live_pub_..."
  user={{ id: session.userId, name: session.name }}
/>

Publishable keys may appear in the client. Secret keys must stay server-side. Live publishable config requires BYO ($9.99) or Hosted ($19.99). Live post storage requires Hosted ($19.99).

BYO adapter mode

Posts live on your API. Implement four callbacks: listPosts, createPost, vote, unvote.

<Feedback
  boardId="brd_..."
  user={currentUser}
  adapter={{
    listPosts: async ({ boardId, status, cursor }) =>
      fetch(`/api/feedback?boardId=${boardId}`).then((r) => r.json()),
    createPost: async (input) =>
      fetch("/api/feedback", {
        method: "POST",
        body: JSON.stringify(input),
      }).then((r) => r.json()),
    vote: async ({ postId, author }) => {
      await fetch(`/api/feedback/${postId}/vote`, {
        method: "POST",
        body: JSON.stringify({ author }),
      });
    },
    unvote: async ({ postId, author }) => {
      await fetch(`/api/feedback/${postId}/unvote`, {
        method: "POST",
        body: JSON.stringify({ author }),
      });
    },
  }}
/>

Full adapter contract: adapter reference.

authorToken (signed identity)

Mint a short-lived token server-side with your Feedback secret key to harden identity against publishable-key leaks. When requireAuthorToken is on in the board config, the token is required for every create and vote.

// Server route: mint a token for the current session user
const res = await fetch(
  "https://feedback.bootstrapware.co/api/v1/author-tokens",
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.BSW_SECRET}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      authorId: session.userId,
      boardId: "brd_...",
      expiresInSec: 3600,
    }),
  }
);
const { authorToken } = await res.json();

// Client: pass the token down
<Feedback
  boardId="brd_..."
  publishableKey="bsw_live_pub_..."
  user={{ id: session.userId, name: session.name }}
  authorToken={authorToken}
/>

Full flow: identity and authorToken.

Statuses and content limits

TopicValue
Statusesopen | planned | in_progress | shipped | declined
Title200 characters max
Body5 000 characters max, plain text
Admin reply2 000 characters max (Hosted moderation)

Common pitfalls

  • Forgetting styles.css
  • Unstable user.id across sessions. Use an opaque stable key from your auth layer, not a display name or email that can change
  • Inventing completed or done instead of shipped
  • Putting secret keys in client code
  • Building a public-facing board URL on top. Product scope is embed-only; there is no /b/... page to link visitors to
  • Skipping origins registration for live keys in production

Related: Architecture guide · Next.js guide · Vite guide · Adapter reference · Identity and authorToken · React component API · Demo