Guide Supabase

Supabase RLS policy examples for Lovable apps

Short answer

Most Lovable apps need only a few Row Level Security (RLS) patterns: each user reaches only their own rows ((select auth.uid()) = user_id), public content anyone can read but only the owner can change, insert-only tables for forms, team data checked against a membership table, and per-user folders in Storage. Turn RLS on, grant each table only to the roles that need it, write one policy per operation, then test signed out and with a second account. Don't use using (true) on private data.

1. Each user sees and edits only their own rows

alter table public.notes enable row level security;
grant select, insert, update, delete on table public.notes to authenticated;

create policy "notes_select_own" on public.notes for select to authenticated
  using ((select auth.uid()) = user_id);
create policy "notes_insert_own" on public.notes for insert to authenticated
  with check ((select auth.uid()) = user_id);
create policy "notes_update_own" on public.notes for update to authenticated
  using ((select auth.uid()) = user_id) with check ((select auth.uid()) = user_id);
create policy "notes_delete_own" on public.notes for delete to authenticated
  using ((select auth.uid()) = user_id);

using filters which existing rows a user can touch; with check validates the row being written, so nobody can create or move a row into someone else's name. Wrapping auth.uid() in select is Supabase's performance recommendation.

2. Public read, owner writes (posts, profiles, listings)

grant select on table public.posts to anon, authenticated;
grant insert, update, delete on table public.posts to authenticated;

create policy "posts_public_read" on public.posts for select to anon, authenticated
  using (published = true);
create policy "posts_owner_write" on public.posts for insert to authenticated
  with check ((select auth.uid()) = author_id);
create policy "posts_owner_update" on public.posts for update to authenticated
  using ((select auth.uid()) = author_id) with check ((select auth.uid()) = author_id);

Only put columns in this table that are fine for anyone to read. Keep emails, phone numbers and similar in a separate private table.

3. Insert-only forms (contact, waitlist, feedback)

grant insert on table public.contact_messages to anon, authenticated;
create policy "contact_insert_only" on public.contact_messages for insert to anon, authenticated
  with check (true);

No select policy and no select grant means visitors can submit but can't read anyone's messages. Read them from the dashboard or server code. Add rate limiting or a captcha if spam becomes a problem.

4. Team or workspace members

create policy "projects_members_read" on public.projects for select to authenticated
  using (exists (
    select 1 from public.memberships m
    where m.project_id = projects.id and m.user_id = (select auth.uid())
  ));

The memberships table needs RLS too (members can read their own membership rows) or the check can be bypassed or leak who belongs where. Make sure users can't insert themselves into a team: only owners should be able to add memberships.

5. Admin-only access

create policy "reports_admin_read" on public.reports for select to authenticated
  using (((select auth.jwt()) -> 'app_metadata' ->> 'role') = 'admin');

Use app_metadata, which only your server or the dashboard can set. Don't base admin checks on user_metadata: users can edit it themselves.

6. Per-user folders in Storage

create policy "avatars_own_folder_read" on storage.objects for select to authenticated
  using (bucket_id = 'avatars' and (storage.foldername(name))[1] = (select auth.uid())::text);
create policy "avatars_own_folder_write" on storage.objects for insert to authenticated
  with check (bucket_id = 'avatars' and (storage.foldername(name))[1] = (select auth.uid())::text);

Upload files to <user id>/filename. Keep the bucket private for private files. More: Is my Supabase storage bucket public?

How to apply these in Lovable

With your own Supabase project, run the SQL in the Supabase SQL editor or ask Lovable to create a migration with it. On Lovable Cloud, paste the policy into the project chat and ask Lovable to apply exactly that; Lovable's docs list your policies under More, then Database, then Manage, then Policies. Then run the Quick scan or Deep scan in the Security view and test as described below.

How to test a policy

  1. Sign out and request the table with only the publishable or anon key: private tables return [] or a 42501 error.
  2. Sign in as user B and try to read and update user A's row: nothing comes back and nothing changes.
  3. Don't test in the SQL editor: it runs as postgres, which bypasses RLS.

Where we can help

Free instant check: paste your URL at patchandship.com/check.html to scan your homepage's JavaScript for secret keys and security headers (no signup, nothing stored). Paid, fixed price per app: Database Check, $49; Security Fix, $149 (every critical issue on our checklist fixed within 48 hours of working access); Fix & Deploy, $249. Otherwise a full refund if you ask within 14 days of purchase (Refund Policy). AI agents run the checks and a human engineer approves every change. Not a penetration test or a security guarantee.

FAQ

Why does my table return an empty list after I enable RLS?

RLS with no matching policy returns no rows, without an error. Add a select policy for the role your app uses.

Is 'auth.uid() is not null' a safe policy?

Only for data every signed-in user may see. Anyone can sign up, so on an open app it's close to public. Compare the owner column to auth.uid() instead.

Do I need one policy per operation?

It's the clearest way: select, insert, update and delete each get their own rule, so you can see exactly what each allows.

Do I still need GRANTs if I have policies?

Yes. Grants decide whether a role can reach the table at all; policies decide which rows. After Supabase's Oct 30, 2026 change, new tables have no API grants until you add them.

Can users change their own role in user_metadata?

Yes, user_metadata is user-editable. Put roles in app_metadata, which only the server can set.

Sources