What this means
Postgres raises 'new row violates row-level security policy for table "your_table"' when row-level security (RLS) is enabled and the row you tried to insert or update does not pass any policy's with check expression. Supabase passes the error back to your app with code 42501. The fix is to add or correct a write policy, not to switch RLS off.
Why it happens
- The table has RLS enabled but no INSERT policy. With no policy, Postgres denies every write from the anon and authenticated roles.
- The policy exists but the row does not match it. A common case in generated code: the policy checks (select auth.uid()) = user_id, and the insert leaves user_id empty or sends a different value.
- The user is not signed in when the request runs, so the request uses the anon role and auth.uid() is null. This often happens when a generated server route creates a Supabase client without the user's session.
- The code calls insert(...).select(). Postgres also checks the new row against your SELECT policies when it has to return it, so a missing SELECT policy fails the insert.
- The code calls upsert. That can take the UPDATE path, which needs an UPDATE policy as well as an INSERT policy.
How to fix it
- 1Open the SQL editor in the Supabase dashboard and check which policies exist on the table: select * from pg_policies where tablename = 'todos';
- 2Add an owner column if the table does not have one, and give it a default of auth.uid() so the client never has to send it.
- 3Create an INSERT policy with a with check clause that compares the owner column with the signed-in user.
- 4Add matching SELECT and UPDATE policies if your code reads the row back or uses upsert.
- 5In your app, confirm the user is signed in before the insert and that the Supabase client carries their session.
alter table public.todos enable row level security;
alter table public.todos
alter column user_id set default auth.uid();
create policy "Users can insert their own todos"
on public.todos for insert
to authenticated
with check ((select auth.uid()) = user_id);
create policy "Users can read their own todos"
on public.todos for select
to authenticated
using ((select auth.uid()) = user_id);
create policy "Users can update their own todos"
on public.todos for update
to authenticated
using ((select auth.uid()) = user_id)
with check ((select auth.uid()) = user_id);How to confirm the fix
Run the test below in the SQL editor of your own project. It acts as one of your users, tries an insert, then rolls back so nothing is saved. Replace the UUID with a real id from auth.users. The first insert should succeed. The second, which claims another user's id, should fail with the same RLS error. That failure is the proof the policy works.
begin;
set local role authenticated;
set local request.jwt.claims = '{"sub":"11111111-1111-1111-1111-111111111111","role":"authenticated"}';
insert into public.todos (title) values ('rls test');
insert into public.todos (title, user_id)
values ('should fail', '22222222-2222-2222-2222-222222222222');
rollback;Frequently asked questions
Can I just disable RLS to get rid of the error?
You can, and the error will stop, but so will the protection. Supabase exposes tables in the public schema over its API, so a table without RLS can be read and changed by anyone who has your project URL and public key. Write a policy instead.
Why does it work in the SQL editor but fail in my app?
The SQL editor runs as a privileged role that bypasses RLS. Your app runs as anon or authenticated, which do not. Test as those roles, as shown above.
Why does my insert fail only when I add .select()?
Returning the new row requires it to pass a SELECT policy too. Add a SELECT policy that lets users read their own rows.
Is using the service_role key on the server a valid fix?
Only for trusted server code that does its own permission checks, such as a webhook handler. The service_role key bypasses RLS entirely, so it must never reach the browser and should not replace policies for normal user actions.