Fix guide · updated 2026-11-07
Supabase RLS: "new row violates row-level security policy"
new row violates row-level security policy for table "profiles"
Your app tries to save something, and Supabase answers with error 42501:
new row violates row-level security policy for table "profiles"
Row-level security (RLS) is Supabase's lock on each table. When it's on, nobody can read or write a row unless a policy says they can. This error means the lock is working: no policy allows this user to save this row. Here are the four reasons that happens, and the fix for each.
1. There's no policy for inserting at all
AI builders often create a policy to read rows and forget the one to add them.
Check: Supabase dashboard → Authentication → Policies → your table. Is there a policy for INSERT?
Fix: in the SQL editor, add one. This version lets signed-in users add rows that belong to them:
create policy "Users can add their own rows"
on public.profiles
for insert
to authenticated
with check ( (select auth.uid()) = user_id );
Replace profiles and user_id with your table and the column that stores the owner.
2. The user isn't actually signed in
The policy above is for authenticated users. If the request goes out before login finishes, or the session expired, Supabase treats it as an anonymous visitor (anon), and the policy doesn't apply.
Check: right before the insert, log the session:
const { data: { session } } = await supabase.auth.getSession();
console.log(session?.user?.id); // undefined = not signed in
Fix: only save after sign-in is confirmed, and send logged-out users to the login page instead of letting the request fail.
3. The owner column is empty
The policy checks that user_id equals the signed-in user. If your code never fills in user_id, the column is empty, the check fails, and you get the same error.
Fix: let the database fill it in automatically:
alter table public.profiles
alter column user_id set default auth.uid();
Now every new row gets the signed-in user's id, even if your code forgets.
4. You read the row back right after saving it
Code like .insert(row).select() saves the row and then asks for it back. Asking for it back is a read, so it also needs a SELECT policy. Without one, the whole request fails with the same message, even though your insert policy is fine.
Fix: add a read policy for the owner:
create policy "Users can read their own rows"
on public.profiles
for select
to authenticated
using ( (select auth.uid()) = user_id );
Using .upsert()? That can also update existing rows, so it needs an UPDATE policy too.
Signed in, but the app shows no data
No error at all, just empty lists. That's RLS too: when no SELECT policy matches, Supabase doesn't complain, it simply returns zero rows. It often shows up right after deploy, because the preview may have been reading the data in a way your live app can't.
Fix: add the read policy from step 4 for each table the page loads, with the right owner column.
The warning "RLS Disabled in Public"
Supabase's Security Advisor shows RLS Disabled in Public when a table has no row-level security at all. That table is open: anyone with your public key can read and change every row. Turn it on (Table Editor → the table → Enable RLS, or alter table public.your_table enable row level security;) and add policies right away, or your app will stop seeing that table's data.
The fix you must never use
You'll find advice to just turn RLS off. Don't. Your Supabase key is inside your app, so anyone can find it in their browser. With RLS off, that key lets any visitor read, change or delete every row in the table: every user's data, not just their own. The error goes away and your customers' data becomes public.
The right fix is always a policy that matches who should be allowed to do what.
Still failing?
The Supabase dashboard's Logs → Postgres shows the exact request that was refused. If your tables have several owners, teams or roles, the policies get trickier, and that's the kind of thing we fix every day.