Adapter
A FeedbackAdapter is how BYO mode talks to your store. Helpers: createLocalAdapter (demo), createByoAdapter (wrap callbacks), createHostedAdapter (used internally when you pass boardId + publishableKey).
Resolve order
- If
adapteris passed, use it (wins over Hosted props). - Else if
boardIdandpublishableKeyare set, use Hosted. - Else fall back to
createLocalAdapter.
Modes and pricing: Modes.
Interface
type FeedbackAdapter = {
listPosts(input: {
boardId: string;
status?: FeedbackStatus;
cursor?: string;
q?: string;
}): Promise<{ posts: FeedbackPost[]; nextCursor: string | null }>;
createPost(input: {
boardId: string;
title: string;
body?: string;
author: FeedbackAuthor;
authorToken?: string;
idempotencyKey?: string;
}): Promise<FeedbackPost>;
vote(input: {
postId: string;
author: FeedbackAuthor;
authorToken?: string;
}): Promise<void>;
unvote(input: {
postId: string;
author: FeedbackAuthor;
authorToken?: string;
}): Promise<void>;
};FeedbackStatus is open | planned | in_progress | shipped | declined. Optional authorToken is a host-signed assertion minted with POST /api/v1/author-tokens (secret key). Required when the board sets requireAuthorToken. See Identity.
Optional q on listPosts is how similar-request search looks past the loaded page. After 4 characters in the compose title, Hosted and BYO debounce listPosts({ q }). Honor q in BYO so duplicates on later pages can still surface. Local mode falls back to in-memory matching. Up to 3 clickable results jump to the post and focus Vote.
BYO example
<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 }),
});
},
}}
/>Routes are illustrative. Enforce authz on your API using the signed-in session, do not trust the browser alone. Enforce one vote per author.id per post if you want honest counts. Forward optional q on your list route so similar requests can search beyond the page currently on screen.
Limits and content
- Title max 200 characters; body max 5000 (plain text).
- Return
FeedbackPostshapes the widget expects (id,boardId,title,body,status,voteCount, author fields, timestamps). - BYO does not emit Bootstrapware
feedback.post_*webhooks, emit your own from your API if needed.
Walkthrough: Own your feedback API.
Related: Modes · Identity · Limits · Quickstart · Postgres