Supabase row-level security
new row violates row-level security policy for table "…"
Row-level security is on for the table, and none of its policies allowed this insert or update.
Before you start: is Supabase itself down?
The short answer
Supabase refused to save the row because a security rule on the table did not allow it. That rule is called row-level security, and it is doing its job: nothing is down.
Usually the rule is missing, the request is not signed in, or the row’s owner column does not match the person who is signed in. The fix is a rule that allows exactly this case. Switching the security off makes the error go away by letting everyone through.
What it means
With row-level security (RLS) switched on, Postgres checks every new or changed row against the table's policies. For an insert, the with check part of a policy decides; if no policy lets the row through, the whole statement is rejected with this error. Supabase's guide gives its error code as 42501.
The error comes from your table's policies, not from an outage.
Common causes
- There is no policy for this action. With RLS on and no policy that applies, Postgres denies everything.
- The request is not signed in. A policy such as
auth.uid() = user_idfails for a signed-out request, because Supabase's guide notes thatauth.uid()is null when nobody is signed in. - The row does not match the check. For example, the owner column is empty or holds someone else's id.
- The code asks for the new row back. supabase-js returns inserted rows only when you chain
.select(), and returning them needs a SELECT policy as well: Postgres checks rows against the table's SELECT policies before handing them back, and fails the statement if they do not pass. Supabase describes the same thing for file uploads to Storage. - It is an update. An UPDATE also needs a SELECT policy that lets the user see the row.
A worked example
This example is made up for this page and built only from the Supabase and PostgreSQL documentation listed under Sources. It is not a story about a real app. A signed-in user saves a note, and the code asks for the saved row back:
await supabase.from('notes').insert({ body: 'Buy lemons', user_id: user.id }).select()The table has one policy, for inserts: to authenticated, with check (auth.uid() = user_id). The insert passes that check. But .select() asks for the new row back, and Postgres only hands back rows that pass a SELECT policy. There is none, so the whole statement fails with this error, even though the insert rule was right. That is the fourth cause above.
The fix is a second policy, for select, with using (auth.uid() = user_id), so people can read back their own rows and nobody else’s. Or drop the .select() if the code does not need the row back.
Ways to fix it
- Write a policy for exactly the case you want, for signed-in users only:
to authenticated, with awith checkon the owner column, as Supabase's guide recommends. If the code reads the row back, add a matching SELECT policy. - Do not switch RLS off to make the error go away, and never put the service role or secret key in browser code. Supabase's docs say that key bypasses RLS and must never be exposed in the browser.
A prompt to fix it
Paste it into your AI coding tool and fill in the parts in angle brackets. It asks the tool to find the cause and show you before it changes anything.
My Supabase insert fails with: new row violates row-level security policy for table "<table>".
Here is the code that does the insert:
<paste it>
Before writing any policy, show me:
1. The current policies on this table (select * from pg_policies where tablename = '<table>').
2. Whether the request is signed in when it runs, and what auth.uid() would be.
3. The exact row being inserted, including the column the policy checks (for example user_id).
4. Whether the insert is followed by .select(), which also needs a SELECT policy.
Then write the smallest policy that allows only this: signed-in users inserting rows whose owner column equals auth.uid(). Use to authenticated and with check. If the code reads the row back, add a matching SELECT policy.
Do not disable RLS. Do not use the service role or secret key in browser code.How to check it is fixed
- Signed in, the insert works. Signed out, it still fails. The second test matters: it shows the policy still protects the table.
- Signed in as a second user, try to insert a row carrying the first user's id. It should fail.
What this page does not cover
- This page is about inserts and updates being refused. If Postgres says
infinite recursion detected in policy(42P17) instead, your policy looks something up in its own table: that is the Policy Recursion prompt. - The error does not say which cause it is. You find out by looking at the table’s policies and at the request, which is what the prompt above asks your AI tool to do before it writes anything.
- Everything here comes from the documentation listed under Sources, read on September 27, 2026. We have not run this example against a live Supabase project.
Tested prompts that help
- Hand It An Error — If the fix above does not do it, or your error is close to this one but not quite, this prompt hands your AI tool the whole error, what you expected, the last thing you changed and what you already ruled out, and makes it name the cause before it changes anything. Test log: Pass, ChatGPT (Pro), September 27, 2026; Pass, Claude.ai (Fable 5.1 Max), September 27, 2026.
- Policy Recursion — If Postgres says infinite recursion detected in policy (42P17) instead, your policy looks up its own table. This prompt explains the loop and fixes it without switching the security off. Test log: Partial, ChatGPT (Pro), September 27, 2026; Pass, Claude.ai (Fable 5.1 Max), September 27, 2026.
- Security Boundary — If you are still deciding who may see or change which rows, this prompt makes your AI tool write that rule down in plain words first, then put it in the database as policies, with a test that one user cannot reach another user's rows. Test log: Pass, ChatGPT (Pro), September 27, 2026; Pass, Claude.ai (Fable 5.1 Max), September 27, 2026.
Where to go next
- Policy Recursion — the prompt for 42P17, when a policy chases its own tail
- Security Boundary — say who may see what, and put that rule in the database
- Is Supabase down? — its live status, from Supabase’s own feed
- The Vibe Check — one of its three blockers asks whether every page checks what each person may touch
Sources
Everything above comes from these pages. We read them on September 27, 2026; if one has changed since, it wins.