Fix "new row violates row-level security policy" in Lovable and Supabase apps
Updated 2026-11-11
What you see
Your app worked in preview. Then you (or the AI builder) turned on Row Level Security, or you deployed and signed in as a real user, and every insert fails:
new row violates row-level security policy for table "todos"
The Supabase client returns this with Postgres error code 42501.
Why it happens
With RLS enabled, Postgres refuses every row that no policy allows. For inserts, the deciding rule is the policy’s with check expression. There are three common causes, all covered in Supabase’s RLS docs:
- No grant: the role (
anonorauthenticated) has noinsertprivilege on the table. This fails before any policy runs. - No insert policy, or a
with checkthat the new row doesn’t satisfy, such as auser_idthat isn’t the signed-in user. - Not signed in:
auth.uid()returnsnullwhen the request has no valid session, andnull = user_idis never true.
AI-generated code often hits #2 by never sending user_id, or hits #3 by inserting before the session has loaded.
Fix it
1. Make sure the row carries the owner. Either send it from the client:
const { data: { user } } = await supabase.auth.getUser();
await supabase.from('todos').insert({ title, user_id: user.id });
or let the database fill it in with a column default:
alter table public.todos
alter column user_id set default auth.uid();
2. Add an insert policy scoped to signed-in users, plus the select policy (updates won’t work without one):
alter table public.todos enable row level security;
create policy "Users can create their own todos"
on public.todos for insert
to authenticated
with check ( (select auth.uid()) = user_id );
create policy "Users can view their own todos"
on public.todos for select
to authenticated
using ( (select auth.uid()) = user_id );
3. Check the grants. If the error persists, confirm authenticated can insert:
grant select, insert, update, delete on table public.todos to authenticated;
4. Confirm a session exists before the insert runs. Call supabase.auth.getUser() first, and redirect to login if it returns no user.
Don’t do this
- Don’t disable RLS to make the error go away. Supabase warns that a table in an exposed schema without RLS can be read and written by anyone holding a grant on it, and your publishable key is in the browser bundle.
- Don’t use the
service_role/ secret key in front-end code. It bypasses RLS completely. Keep it server-side. - Don’t write
using (true)unless the data is meant to be public.
Check it worked
Sign in as user A and insert a row: it should succeed. Sign in as user B and try to read it: you should get no rows back, not an error. Supabase recommends locking this in with supabase test db (pgTAP) tests for each table.