Supabase RLS for vibe coders: the 8 rules your AI agent keeps breaking

Published October 14, 2026

Row Level Security (RLS) is the only thing standing between your Supabase anon key, which is public by design, and every row in your database. AI agents often create tables without it, or they write policies that look right and allow everything. Here are the rules we bake into our packs.

1. Enable RLS in the same migration that creates the table

create table public.projects (...);
alter table public.projects enable row level security;

If RLS is off, the anon key can read and write the whole table through the REST API. Check every table with:

select tablename from pg_tables where schemaname = 'public' and not rowsecurity;

2. One policy per operation

Write separate select, insert, update and delete policies instead of a single for all. They're easier to review and harder to get wrong. The exception is a profiles table with billing columns: give it no insert or delete policy, and limit updates with a column grant (grant update (full_name)). Otherwise a user can insert or update their own row with plan = 'pro'.

3. Use (select auth.uid()), not auth.uid()

Wrapping it in a sub-select lets Postgres evaluate it once per statement instead of once per row. Supabase's own performance guidance recommends this.

create policy "select own" on public.projects for select to authenticated
  using ((select auth.uid()) = user_id);

4. Update policies need using and with check

using controls which rows a user can target, and with check controls what the row may look like afterwards. Without with check, a user can reassign their row to someone else.

5. Index policy columns

create index on public.projects (user_id); Unindexed policy columns make every query a full scan.

6. Never authorize with user_metadata

Users can edit their own user_metadata, so store roles in app_metadata or a table.

7. Protect billing columns

Let users update only safe columns with a column grant, so nobody can set plan = 'pro' from the browser:

revoke update on public.profiles from authenticated;
grant update (full_name) on public.profiles to authenticated;

8. Lock down security definer functions

Always add set search_path = '' and fully qualify table names.

Test it as two users

Sign in as user A, insert a row, then sign in as user B and confirm B can't select, update or delete it. Our Next.js + Supabase pack includes a prompt that makes your agent write this test for you.