Supabase "new row violates row-level security policy": 5 causes and the safe fix
If your Lovable, Bolt or Cursor app fails to save with new row violates row-level security policy for table "…" (Postgres error 42501), Row Level Security is doing its job: no policy allows this row to be written by this user. The right fix is a policy that matches what your app does. The fix AI tools often suggest is to turn RLS off, which lets anyone with your public anon key write anything.
Cause 1: there is no INSERT (or UPDATE) policy
With RLS on, everything is denied until a policy allows it. A table with only a select policy can be read but not written. Check what policies exist:
select policyname, cmd, roles, qual, with_check
from pg_policies where tablename = 'notes';
If there is no row with cmd = INSERT (or ALL), add one:
create policy "Users add their own notes"
on public.notes for insert
to authenticated
with check (auth.uid() = user_id);
Cause 2: the row's user_id is empty or someone else's
The policy above only passes when the row's user_id equals the signed-in user. AI-written code often forgets to send user_id, so it arrives as null and the check fails. Let the database fill it in instead of trusting the browser:
alter table public.notes
alter column user_id set default auth.uid();
Cause 3: the user isn't actually signed in
If the request runs before the session loads, or the user logged out, it runs as anon, and a policy to authenticated doesn't apply. Log (await supabase.auth.getSession()).data.session right before the insert. If it's null, fix the sign-in flow, not the policy.
Cause 4: insert().select() or upsert needs more than an insert policy
.insert(...).select() reads the new row back, so it also needs a select policy that lets the user see it. .upsert(...) can update an existing row, so it needs insert, update and select policies. If the plain insert works but the version with .select() fails, this is your cause.
Cause 5: file uploads to Supabase Storage
If the error appears when uploading a file, the table is storage.objects. Add a policy that lets users upload only into their own folder:
create policy "Users upload to their own folder"
on storage.objects for insert
to authenticated
with check (
bucket_id = 'avatars'
and (storage.foldername(name))[1] = auth.uid()::text
);
Then upload to a path that starts with the user's id, like `${user.id}/avatar.png`.
The fixes that leave your data open
Each of these makes the error go away and lets anyone with your anon key, which is in your app's JavaScript, write to the table:
alter table … disable row level security: every row becomes readable and writable from the browser.with check (true)orusing (true)on user data: anyone can insert rows as any user, or change other people's rows.- A policy
to anonon a table of user data: logged-out visitors never need to write your users' rows. - Using the service_role key in the frontend: it skips RLS completely, and anyone can copy it from your site.
A prompt to paste into Lovable, Bolt or Cursor
Saving fails with "new row violates row-level security policy".
Fix it without weakening security:
1. Show me the current policies on this table (pg_policies).
2. Add only the missing policy for what the code does (insert, update,
select after insert, or upsert), for the authenticated role, with
auth.uid() = user_id in both USING and WITH CHECK.
3. Set user_id to default auth.uid() instead of sending it from the browser.
4. Do not disable RLS, do not use true in a policy on user data, do not
add policies for anon, and do not use the service_role key in the frontend.
Show me the SQL before running it.
Not sure what else is open? LeakPatrol's free scan checks your live app from the outside for leaked keys and public files in about a minute, and our RLS policy generator writes the policies for common table types.
Check your app in a minute
Free, read-only, no sign-up. Every problem comes with a plain-English fix and a prompt for your AI tool.
Get the 10-point checklist
Every check we run on AI-built apps, with how to do each one yourself. Straight to your inbox.
Plus an email when automatic re-checks launch, and the odd tip. Unsubscribe any time.
Questions
What does "new row violates row-level security policy" mean in Supabase?
Row Level Security is on for the table and no policy allows the current user to write that row. Usually there is no insert or update policy, the row's user_id does not match the signed-in user, or the request runs as anon because the user is not signed in.
Should I disable RLS to fix it?
No. With RLS off, anyone who has your public anon key, which is in your app's JavaScript, can read and change every row. Add the missing policy instead.
Why does insert work but insert().select() fail?
insert().select() reads the new row back, so it also needs a select policy that lets the user see the row. upsert additionally needs an update policy.
How do I fix it for Supabase Storage uploads?
Add an insert policy on storage.objects for the authenticated role that checks the bucket and that the first folder in the file path equals auth.uid(), then upload files under the user's id.
More guides
- Supabase RLS policy generator (free)
- Is this API key safe in my frontend? Free key checker
- Security scan MCP server for Claude Code, Cursor and other AI agents
- Supabase's October 30 change: what it fixes for your app's security, and what it doesn't
- Supabase "42501 permission denied for table": the safe fix (not GRANT ALL)
- Supabase service_role key exposed? What to do in the next hour
- Lovable security check: is your Lovable app safe?
- Bolt.new security check: is your Bolt app safe?
- Launch checklist for apps built with Lovable, Bolt and Cursor
- Supabase security check: is your anon key safe? (RLS checker)
- How to check if your API key is exposed on your website
- Are VITE_ env variables secret? How to hide API keys
- Stop users giving themselves your paid plan (Supabase)