Store feedback posts in Supabase

Prefer Supabase Auth, RLS, and your own backups? Use BYO: Bootstrapware hosts published board config; your Route Handler writes Supabase. Post title and body never leave your infrastructure.

There is no first-party Bootstrapware→Supabase adapter. You implement listPosts, createPost, vote, and unvote, then call your API from the widget. Live publishable config still needs a BYO ($9.99) or Hosted ($19.99) entitlement, skip Hosted post storage if Supabase is the source of truth.

Division of responsibility

Bootstrapware handles the embed UX, optional published board settings, and (if you choose Hosted) a separate store path. With BYO, your app owns authentication, authorization, RLS, and every write to feedback_posts / feedback_votes.

Pass host-asserted user={{ id, name? }} from your session. Bootstrapware does not authenticate end users. See identity.

Widget → your Route Handler

The adapter talks to your API, not to Supabase from the browser with a service role. Prefer a server client (or Edge Function) that checks the session, then applies RLS / tenant rules.

<Feedback
  boardId="brd_..."
  user={currentUser}
  adapter={{
    listPosts: async ({ boardId, status, cursor }) =>
      fetch(`/api/feedback?boardId=${boardId}${status ? `&status=${status}` : ""}`).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",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ author }),
      });
    },
    unvote: async ({ postId, author }) => {
      await fetch(`/api/feedback/${postId}/unvote`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ author }),
      });
    },
  }}
/>

Enforce title ≤ 200 and body ≤ 5000 on the server to match the widget (TITLE_MAX / BODY_MAX). Statuses: open | planned | in_progress | shipped | declined, not completed.

Illustrative customer schema (not Bootstrapware tables)

The SQL below is an example for your Supabase project. It is not the Hosted schema and not something Bootstrapware creates for you.

-- Illustrative customer schema, your tables, not Bootstrapware Hosted tables.
create table feedback_posts (
  id text primary key,
  org_id uuid not null references orgs (id),
  board_id text not null,
  title text not null check (char_length(title) <= 200),
  body text check (body is null or char_length(body) <= 5000),
  status text not null check (
    status in ('open', 'planned', 'in_progress', 'shipped', 'declined')
  ),
  vote_count integer not null default 0,
  author_id text not null,
  author_name text,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now(),
  archived_at timestamptz,
  merged_into_post_id text
);

create table feedback_votes (
  post_id text not null references feedback_posts (id),
  author_id text not null,
  created_at timestamptz not null default now(),
  primary key (post_id, author_id)
);

alter table feedback_posts enable row level security;
alter table feedback_votes enable row level security;
-- Add policies that scope rows to auth.uid() / org membership.

Map adapter responses to the FeedbackPost shape (id, boardId, title, body, status, voteCount, authorId, timestamps, etc.). One vote per author.id per post, match that with the primary key above.

Session auth and RLS

  • Do not expose the service role as NEXT_PUBLIC_ / VITE_. Use it only on the server if needed, and still enforce tenant checks.
  • Trust your session for who may create or vote; treat author.id from the client as asserted by the host, your API should overwrite or verify it against the logged-in user.
  • Optional live board config: pass boardId + publishable key (or fetch config yourself). Secrets stay server-side.

Pitfalls

  • Calling Supabase from the browser with a service role “because it is easier.”
  • Inventing status completed instead of shipped.
  • Assuming BYO emits Bootstrapware feedback.post_created webhooks, those are Hosted-only.
  • Skipping title/body length checks and getting client/server mismatch.

Related: Postgres BYO · Adapter · BYO vs Hosted · Modes · Own your API