Own your feedback API with FeedbackAdapter

Most teams that care about data residency want the widget without shipping post content to a vendor. That is BYO mode.

Bootstrapware hosts board configuration (toggles, branding, publish revisions). Your backend stores posts and votes. You pass a FeedbackAdapter: or wrap the same four callbacks with createByoAdapter.

The four methods

  • listPosts: return { posts, nextCursor }; optional status filter, cursor, and q (similar-request search beyond the loaded page).
  • createPost: accept title/body/author; return a FeedbackPost.
  • vote / unvote: keyed by postId + author.

Full TypeScript shape: Adapter reference. Limits: title 200 characters, body 5000, plain text. Statuses: open | planned | in_progress | shipped | declined.

Minimal adapter wiring

<Feedback
  boardId="brd_..."
  user={currentUser}
  adapter={{
    listPosts: async ({ boardId, status, cursor, q }) => {
      const params = new URLSearchParams({ boardId });
      if (status) params.set("status", status);
      if (cursor) params.set("cursor", cursor);
      if (q) params.set("q", q);
      return fetch(`/api/feedback?${params}`).then((r) => r.json());
    },
    createPost: async (input) =>
      fetch("/api/feedback", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        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 }),
      });
    },
  }}
/>

Your routes are illustrative. Feedback does not prescribe your URL layout, only the adapter contract the widget calls. Forward optional q so similar requests can search beyond the page currently on screen.

What you must enforce

  • Authz on the server. Do not trust the browser alone. Derive the author from your signed-in session when writing.
  • One vote per author per post. Hosted enforces this; BYO must match if you want honest counts.
  • Stable opaque ids. user.id / author.id should not be emails you plan to rotate.

Pricing and resolve order

Live publishable board config still needs BYO ($9.99) or Hosted ($19.99). Passing an adapter means posts stay with you , you do not need Hosted ($19.99) storage. Explicit adapter always wins over Hosted props.

Compare modes: BYO vs Hosted. Postgres walkthrough: Store posts in Postgres.

What Bootstrapware does not do in BYO

  • No Hosted inbox for your rows.
  • No feedback.post_created / feedback.status_changed webhooks for your store, emit your own from your API if needed.
  • MCP still configures boards only; never send title/body through MCP.

Related: Adapter · Modes · BYO vs Hosted · Postgres store · Identity · Pricing