Skip to main content
RapidDev - Software Development Agency
App Featurescommunication-social18 min read

How to Add Feedback Forms to Your App (Copy-Paste Prompts Included)

A feedback form needs a react-hook-form UI with NPS (0-10) and star rating (1-5) inputs, a Supabase table that accepts both authenticated and anonymous submissions, and RLS that blocks regular users from reading each other's responses. With Lovable you can ship a working form plus an admin view in 1-3 hours for $0/mo — Supabase and Resend free tiers cover most apps indefinitely.

4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members

Feature spec

Beginner

Category

communication-social

Build with AI

1-3 hours with Lovable or v0

Custom build

3-5 days custom dev

Running cost

$0/mo for most apps, $25/mo Supabase Pro at scale

Works on

WebMobile

Everything it takes to ship Feedback Forms — parts, prompts, and real costs.

TL;DR

A feedback form needs a react-hook-form UI with NPS (0-10) and star rating (1-5) inputs, a Supabase table that accepts both authenticated and anonymous submissions, and RLS that blocks regular users from reading each other's responses. With Lovable you can ship a working form plus an admin view in 1-3 hours for $0/mo — Supabase and Resend free tiers cover most apps indefinitely.

What a Feedback Form Feature Actually Is

A feedback form is the lightweight channel through which users tell you what is working and what is not — an NPS survey after onboarding, a star rating after a support interaction, an open text box at the bottom of any screen. The data it produces is only useful if you can read it: an admin view to filter by date, rating, and category is as important as the form itself. The architecture is deliberately simple — a validated form, a Supabase INSERT, and RLS that lets anyone submit but restricts reads to admins only. The beginner mistakes are double-submits on mobile and anonymous INSERT policies that the AI forgets to add.

What users consider table stakes in 2026

  • Single-page or multi-step form with minimal friction — no account creation required to leave feedback
  • NPS scale (0-10) or star rating (1-5) presented as a clear visual selector, not a plain number input
  • Open text field for qualitative input with a character counter so users know the limit
  • Confirmation or thank-you screen after submit so users know their feedback was received
  • Anonymous submission option that does not require the user to be logged in
  • Admin view to read, filter, and aggregate responses — a form without a reader is useless

Anatomy of the Feature

Six components make up a production feedback form. AI tools generate the form UI reliably — the anonymous RLS policy and the admin SELECT restriction are where builds fail.

Layers:UIDataBackendService

Form UI component

UI

Built with react-hook-form v7 and zod validation. Uses shadcn/ui Form, Input, Textarea, RadioGroup, and Slider components. The form controller manages the rating value, category selection, and text field — all wired to zod schema for type-safe validation before submission.

Note: Always wrap the star rating or NPS selector in a react-hook-form Controller — uncontrolled custom rating components lose their value on re-render, which is the most common bug in multi-step forms.

NPS and star rating widget

UI

Two separate question types: an NPS selector showing buttons labeled 0-10 with 'Not at all likely' and 'Extremely likely' anchors, and a 1-5 star rating using Lucide Star icons that fill on hover and click. Both store their value as an integer in the same zod-validated field.

Note: react-stars is a ready-made alternative to custom Lucide icons for star ratings, but it adds a dependency. Custom Lucide implementation is smaller and more maintainable.

Submission handler

Backend

A Supabase Server Action or Edge Function that receives the validated form data and INSERTs a row into the feedback table. Rate-limited to one submission per user (or per IP for anonymous) per 24 hours via a unique constraint or RLS check.

Note: Use INSERT ... ON CONFLICT DO NOTHING with a unique constraint on (user_id, DATE(created_at)) to silently reject duplicate submissions. For anonymous users, rate limiting requires either a client-side submission flag in localStorage or server-side IP tracking.

Response storage

Data

A single Supabase PostgreSQL feedback table with user_id (nullable for anonymous), rating, category, body text, metadata jsonb for custom fields, and created_at. The nullable user_id is what makes anonymous submissions possible.

Note: The metadata jsonb column lets you add new question types later without schema migrations — store conditional question answers, app version, or device type as key-value pairs.

Admin response viewer

UI

A shadcn/ui DataTable component on an admin-only route showing all feedback rows. Filterable by date range, rating (min/max), and category. The aggregate NPS score (percentage of promoters minus percentage of detractors) is computed in a Supabase SQL view or RPC function and displayed at the top.

Note: NPS calculation: ((rows where rating >= 9) - (rows where rating <= 6)) / total * 100. A simple Supabase RPC function keeps this calculation server-side and avoids pulling all rows to the client.

Email notification on submit

Service

A Supabase Edge Function triggered by an INSERT on the feedback table calls the Resend API to send an email to the admin with the rating, category, and body text. Sent only when rating is below a threshold (e.g., NPS 0-6) to avoid inbox overload for high-volume apps.

Note: Resend free tier covers 3,000 emails/mo — more than sufficient for most feedback volumes. Only upgrade to Resend Pro ($20/mo) if admin notification emails exceed that limit.

The data model

One table stores all feedback with anonymous-safe RLS. Admin access requires a role check on the JWT. Run this in the Supabase SQL editor.

schema.sql
1create table public.feedback (
2 id uuid primary key default gen_random_uuid(),
3 user_id uuid references auth.users(id) on delete set null,
4 rating int not null check (rating >= 0 and rating <= 10),
5 category text not null default 'General' check (category in ('Bug Report', 'Feature Request', 'General')),
6 body text check (char_length(body) <= 500),
7 metadata jsonb default '{}',
8 created_at timestamptz not null default now()
9);
10
11alter table public.feedback enable row level security;
12
13-- Authenticated users can submit feedback
14create policy "authenticated insert"
15 on public.feedback for insert
16 to authenticated
17 with check (auth.uid() = user_id);
18
19-- Anonymous users can submit feedback (user_id will be null)
20create policy "anon insert"
21 on public.feedback for insert
22 to anon
23 with check (user_id is null);
24
25-- Only admins can read feedback
26create policy "admin select"
27 on public.feedback for select
28 using (auth.jwt() ->> 'role' = 'admin');
29
30-- Unique constraint prevents duplicate submissions per user per day
31create unique index feedback_user_daily_unique
32 on public.feedback (user_id, (created_at::date))
33 where user_id is not null;
34
35create index feedback_created_at_idx
36 on public.feedback (created_at desc);
37
38create index feedback_rating_idx
39 on public.feedback (rating);
40
41-- RPC function to compute NPS score
42create or replace function public.compute_nps()
43returns numeric
44language sql
45security definer
46as $$
47 select
48 round(
49 (
50 count(*) filter (where rating >= 9)::numeric
51 - count(*) filter (where rating <= 6)::numeric
52 ) / nullif(count(*), 0) * 100
53 , 1)
54 from public.feedback;
55$$;

Heads up: The unique index on (user_id, created_at::date) prevents authenticated users from submitting more than once per day at the database level — the application-layer guard is a second line of defence. The compute_nps() RPC function uses security definer to run with elevated privileges so it works even when the admin JWT is not present in a server-side call.

Build it — pick your path

Each tool fits this feature differently. Switch tabs to compare fit, time, the exact steps, and a copy-paste prompt.

Full-stack, non-technical friendlyFit for this feature:

Best path for beginners: Lovable generates the complete form, Supabase table, anonymous RLS, and admin view in a single prompt with no manual wiring required.

Step by step

  1. 1Create a new Lovable project and connect Lovable Cloud so Supabase is provisioned automatically
  2. 2Paste the prompt below in Agent Mode — Lovable will create the feedback table with RLS, the form component, and the admin view in one pass
  3. 3Open the Cloud tab and verify the feedback table exists with RLS enabled; check that both the authenticated and anon INSERT policies are present
  4. 4Add RESEND_API_KEY to the Secrets panel (Cloud tab) to enable admin email notifications
  5. 5Publish and test by submitting feedback while logged out (anonymous) and while logged in (authenticated)
Paste into Lovable
Build a feedback form feature using Supabase. Create a feedback table: id (uuid pk), user_id (uuid nullable references auth.users), rating (int 0-10 NPS scale), category (text: 'Bug Report', 'Feature Request', 'General'), body (text max 500 chars), metadata (jsonb), created_at (timestamptz). Enable RLS: authenticated users can INSERT with user_id = auth.uid(); anonymous users (role = anon) can INSERT with user_id null; only users with JWT role = 'admin' can SELECT. Add a unique constraint preventing the same user from submitting more than once per calendar day. UI: Feedback form page with two question types — NPS selector (buttons 0-10 with 'Not at all likely' / 'Extremely likely' labels) and star rating (1-5 Lucide Star icons). Category dropdown with three options. Textarea for qualitative feedback with character counter showing remaining of 500 max. Submit button shows loading state during insert. On success, show a thank-you screen with 'Back to app' link. Wrap NPS and star inputs in react-hook-form Controller so values persist in multi-step forms. Validate with zod: rating required, body optional but max 500 chars. Add a Supabase Edge Function that calls Resend API (RESEND_API_KEY in Secrets) to email the admin when a new submission arrives — include rating, category, and body in the email. Admin page at /admin/feedback: shadcn/ui DataTable listing all rows, filterable by date range and category, showing average NPS score at the top computed via a Supabase RPC function. Handle the empty state when no feedback exists yet.

Where this path bites

  • Multi-step conditional logic (show question B only if answer to A is X) requires manual refinement — Lovable's first pass usually generates all questions on a single screen
  • The admin JWT role check assumes you have set a custom role claim in Supabase Auth; if not, admin access requires a separate profiles table with an is_admin boolean

Third-party services you'll need

Feedback forms need two services at most — a database for storage and an email service for admin notifications. Both have generous free tiers.

ServiceWhat it doesFree tierPaid from
SupabasePostgreSQL storage for feedback rows, RLS for access control, Edge Functions for notification dispatchFree: 500 MB DB, unlimited rows within storage limitPro $25/mo for production uptime guarantees and larger storage
ResendAdmin email notification on new feedback submissionFree: 3,000 emails/mo — sufficient for most admin notification volumesPro $20/mo: 50,000 emails (approx)

Swipe the table sideways to see pricing.

What it costs to run

Drag through the tiers to see how your monthly bill scales with users — no surprises later.

Estimated monthly running cost

$0/mo

Supabase free tier handles the submission volume. Resend free tier covers admin notifications. No paid services required.

Estimates use verified 2026 pricing and assume typical usage per user. Your real bill depends on activity, storage, and third-party plans.

What breaks when AI tools build this

The failures people actually hit on their first build — the symptom, why it happens, and the exact fix.

Form submits twice on mobile

Symptom: React StrictMode double-invokes effects and event handlers in development — combined with a slow network causing the user to tap Submit twice, the Edge Function receives two identical POST requests within milliseconds. The result is two identical feedback rows with the same user_id and timestamp.

Fix: Add a client-side isSubmitting boolean guard that sets true on first submit and prevents a second call until the promise resolves. At the database level, add a unique index on (user_id, (created_at::date)) and use INSERT ... ON CONFLICT DO NOTHING so even if a duplicate reaches the server, only one row is inserted.

Anonymous submissions blocked by RLS

Symptom: AI-generated RLS typically includes only an authenticated INSERT policy that checks auth.uid() IS NOT NULL. Anonymous users making requests with the Supabase anon key are assigned the 'anon' role — they have no uid, so the policy rejects them with 'new row violates row-level security policy' even though no login should be required.

Fix: Add a separate anon policy: CREATE POLICY "anon insert" ON feedback FOR INSERT TO anon WITH CHECK (user_id IS NULL). Make sure user_id is nullable in the table schema. Test by submitting the form in an incognito window without logging in.

Admin table shows all feedback to any authenticated user

Symptom: AI generates a SELECT policy for 'authenticated' role without restricting to admins — any logged-in user navigating to /admin/feedback can read every response. This is a data privacy issue, not just a UX problem.

Fix: Replace the broad SELECT policy with one that checks the admin role: CREATE POLICY "admin select" ON feedback FOR SELECT USING (auth.jwt() ->> 'role' = 'admin'). Set the admin role claim in Supabase Auth using a custom JWT hook or by storing it in the profiles table and reading it server-side with the service role key.

Star rating resets on re-render

Symptom: Custom star rating components built as uncontrolled React elements (internal useState without react-hook-form integration) lose their value whenever the parent form re-renders — for example, when the character counter updates on each keystroke. The user sees their 5-star rating reset to 0 while typing their comment.

Fix: Wrap the star rating widget in a react-hook-form Controller component and use the field.value and field.onChange props to keep the value in the form state instead of local component state. Set defaultValue on the Controller.

Best practices

1

Always write both an authenticated INSERT policy and a separate anon INSERT policy — anonymous feedback is often your most candid signal

2

Add a unique constraint at the database level on (user_id, created_at::date) to prevent duplicate submissions even if your client-side guard fails

3

Restrict admin SELECT with a role check, not just route-level authentication — any authenticated user who discovers the admin URL should not see all feedback

4

Wrap all custom rating inputs in react-hook-form Controller — uncontrolled components lose state on any parent re-render

5

Send admin email notifications only for low-rating submissions (NPS 0-6 or 1-2 stars) to keep the signal-to-noise ratio high

6

Store metadata (app version, device type, current page) in the jsonb column from day one — you will want to correlate feedback with context later

7

Show a character counter on the open text field from the start — without it, users either write nothing or hit an invisible limit

When You Need Custom Development

Lovable handles the standard feedback form well. A few requirements push beyond what AI tools can generate reliably:

  • You need advanced branching or skip logic across 10+ questions — the kind of conditional survey flow Typeform is built for
  • You need to sync responses to HubSpot, Salesforce, or Intercom via webhooks in real time as submissions arrive
  • You need GDPR data residency in an EU region with a right-to-erasure workflow that processes deletion requests within 30 days
  • You need real-time sentiment analysis on open-text responses using an LLM API to auto-classify and route critical feedback

RapidDev builds these features for production

Real auth, database, and payments — fixed price, delivered in weeks. You own 100% of the code.

Get a fixed-price quote

Frequently asked questions

What is the simplest way to add a feedback form to a Lovable app?

One Lovable prompt covers the full feedback form: specify NPS scale (0-10), star rating (1-5), a category dropdown, an open text field with a 500-character limit, and a thank-you screen. Lovable creates the Supabase table, the RLS policies, and the form component automatically. The entire build takes 1-2 hours.

Can users submit feedback anonymously?

Yes — make user_id nullable in the feedback table and add a separate Supabase RLS policy for the anon role: CREATE POLICY "anon insert" ON feedback FOR INSERT TO anon WITH CHECK (user_id IS NULL). Without this second policy, the default authenticated-only INSERT policy blocks all logged-out submissions. Test by submitting in an incognito window.

How do I see all feedback responses in one place?

Add an admin page with a shadcn/ui DataTable component that reads from the feedback table using the Supabase service role key (server-side only). Apply date range and category filters and display the aggregate NPS score at the top via a SQL RPC function. Restrict access to this page with a role check — not just a hidden URL.

What is the difference between NPS and star rating?

NPS (Net Promoter Score) uses a 0-10 scale and asks 'how likely are you to recommend this?' — respondents are grouped into promoters (9-10), passives (7-8), and detractors (0-6), and the score is the promoter percentage minus the detractor percentage. Star rating (1-5) measures satisfaction with a specific interaction. Use NPS for product-level health tracking and star rating for per-interaction quality scoring.

How do I stop spam submissions from bots?

Two layers: a unique constraint at the database level on (user_id, created_at::date) prevents more than one authenticated submission per day. For anonymous submissions, add a honeypot hidden field that bots fill in but real users ignore — reject any submission where the honeypot field is not empty. Cloudflare Turnstile is the strongest option if spam is a serious concern.

Can I send feedback to my email automatically?

Yes — a Supabase Edge Function triggered by an INSERT on the feedback table calls the Resend API to email you the rating, category, and body. Resend free tier covers 3,000 emails/month, which is more than sufficient unless you are sending a notification for every single submission from a high-traffic app. Filter to only critical ratings (NPS 0-6) to keep the volume manageable.

How do I add conditional questions that only show based on previous answers?

Conditional logic (show question B only if answer A is below 7) requires storing the form schema as a JSONB object and rendering questions dynamically based on the previous answer. Lovable's first-pass generates a static form — you need to follow up with a prompt specifying the exact condition. For complex multi-branch surveys, a custom build or a dedicated tool like Typeform is more practical.

Is there a free way to store feedback responses?

Yes. Supabase free tier stores up to 500 MB of data with unlimited rows — a feedback table row is roughly 1 KB, so the free tier holds hundreds of thousands of responses before any cost is incurred. You only need Supabase Pro ($25/mo) when you need production uptime guarantees, not because of storage volume.

RapidDev

Need this feature production-ready?

RapidDev builds feedback forms into real apps — auth, database, payments — at $13K–$25K.

Book a free consultation

30-min call. No commitment.

Want this built for you?

We ship production apps at a fixed price — $13K–$25K, 6–10 weeks, source code yours. You've seen what it takes; we do it every week.

Get a fixed-price quote

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.