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
| Prop | Type | Purpose |
|---|---|---|
| user | { id, name?, email?, avatarUrl? } | Required. Your app asserts identity; Bootstrapware does not auth end users. |
| adapter | FeedbackAdapter | BYO or local adapter. Pass this to own posts. Takes precedence over Hosted. |
| boardId | string | Hosted board ID. Required together with publishableKey for Hosted mode. |
| publishableKey | string | Publishable key (bsw_*_pub_). Safe in client code. Required for Hosted mode. |
| authorToken | string? | Short-lived signed assertion minted server-side. Required when board has requireAuthorToken: true. See identity. |
| apiBaseUrl | string? | Override API origin. Defaults to https://feedback.bootstrapware.co. |
Mode resolution order
- Explicit
adapterprop → that adapter is used (BYO or local). boardId+publishableKey→ Hosted adapter is constructed automatically.- 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
| Topic | Value |
|---|---|
| Statuses | open | planned | in_progress | shipped | declined |
| Title | 200 characters max |
| Body | 5 000 characters max, plain text |
| Admin reply | 2 000 characters max (Hosted moderation) |
Common pitfalls
- Forgetting
styles.css - Unstable
user.idacross sessions. Use an opaque stable key from your auth layer, not a display name or email that can change - Inventing
completedordoneinstead ofshipped - 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