Short answer: Supabase shows new row violates row-level security policy for table "..." (Postgres error 42501) when Row Level Security is on and no policy allows the row you are writing. The fix is to add or correct a policy, not to turn RLS off. For most apps that means an insert policy with with check ((select auth.uid()) = user_id), and making sure your app actually sends user_id and a logged-in session. If the error appears on file uploads, .insert().select() or upsert, you also need a matching select (and for upsert, update) policy.
This is one of the most common errors in apps built with Lovable, Bolt and other AI tools that use Supabase. The tempting fix, disabling RLS, is how apps end up with their whole database readable by anyone.
What the error means
When RLS is enabled on a table, Postgres refuses every read and write that no policy allows. Supabase's docs put it simply: once RLS is on, no data is accessible through the API with the public key "until policies are created" (Supabase RLS docs).
For writes, the rule that matters is the policy's with check clause. Postgres runs it against the new row. If it returns false, or if there is no insert policy at all, you get new row violates row-level security policy.
Cause 1: there is no insert policy
Many generated schemas have a select policy but no insert policy. Add one:
create policy "Users can create their own rows"
on public.todos for insert
to authenticated
with check ( (select auth.uid()) = user_id );
This is the same shape as the example in Supabase's RLS guide. Wrapping auth.uid() in (select ...) is Supabase's own performance tip: Postgres works it out once per statement instead of once per row.
Cause 2: the row doesn't pass the check
The policy exists, but the row you send fails it. Common reasons:
user_idis missing or null. The app doesn't send it, soauth.uid() = user_idis false. Either send it from the app or give the column a default:alter table public.todos alter column user_id set default auth.uid();- The user isn't logged in, or the session isn't attached. If the request goes out with only the public key and no user token,
auth.uid()is null and every check fails. This often happens right after sign-up when email confirmation is on, or in server code that creates a fresh client without the user's session. - The policy is for the wrong role. A policy
to authenticateddoesn't apply to logged-out visitors. If anonymous visitors really should insert (a contact form, for example), write a separate, narrow policy foranonthat only allows that table and the columns you expect. - The ID column is a different type. If
user_idistextandauth.uid()returns auuid, the comparison can behave unexpectedly. Keepuser_idasuuidand referenceauth.users(id).
Cause 3: insert works, but the app still errors
If you use supabase.from('todos').insert(row).select(), the database has to return the new row to you. That return is a read, so it needs a select policy that also matches the row. Without one, the whole request can fail with the same RLS error even though your insert policy is fine.
The same applies to upsert, which may update an existing row. It needs insert, update and select policies that all match. Supabase's docs also note that an update needs a matching select policy to work as expected (Supabase RLS docs).
create policy "Users can read their own rows"
on public.todos for select
to authenticated
using ( (select auth.uid()) = user_id );
create policy "Users can update their own rows"
on public.todos for update
to authenticated
using ( (select auth.uid()) = user_id )
with check ( (select auth.uid()) = user_id );