Skip to content
PixelRidge
← All guides

29 September 2026 · 5 min read

"new row violates row-level security policy" in Supabase: the real fixes (without turning RLS off)

Why Supabase (and Lovable or Bolt apps) throw "new row violates row-level security policy" on insert, upload or upsert, how to fix each cause with the right policy, and why disabling RLS is the wrong answer.

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_id is missing or null. The app doesn't send it, so auth.uid() = user_id is 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 authenticated doesn't apply to logged-out visitors. If anonymous visitors really should insert (a contact form, for example), write a separate, narrow policy for anon that only allows that table and the columns you expect.
  • The ID column is a different type. If user_id is text and auth.uid() returns a uuid, the comparison can behave unexpectedly. Keep user_id as uuid and reference auth.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 );

Cause 4: file uploads (Storage 403)

On uploads the error arrives as a 403 Forbidden with the same message. Storage writes a row to storage.objects and then reads it back, so it needs both an insert policy and a select policy on storage.objects (Supabase troubleshooting). A common pattern is one folder per user:

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] = (select auth.uid())::text
);

create policy "Users read their own files"
on storage.objects for select
to authenticated
using (
  bucket_id = 'avatars'
  and (storage.foldername(name))[1] = (select auth.uid())::text
);

Then upload to a path that starts with the user's ID, like `${user.id}/avatar.png`.

The fixes that make things worse

Two "fixes" make the error go away and open a much bigger hole:

  1. Disabling RLS, or a policy of using (true) / with check (true). Now anyone with your app's public key, which is in every visitor's browser, can read or change the whole table. This is what happened in CVE-2025-48757: a researcher found 170+ Lovable-built projects with missing or weak RLS, exposing data such as emails, phone numbers and payment status (Matt Palmer). Lovable disputes the CVE, saying each customer is responsible for their app's data. Either way, the data was exposed.
  2. Putting the service_role or secret key in the frontend. It bypasses RLS, so the error disappears, but so does all your protection. Supabase says: "Never use a secret key in the browser or expose it to customers." If this has already happened, rotate the key; our guide on Supabase publishable and secret keys shows how.

Check that the fix really works

Don't stop at "the error went away". Test with two accounts:

  1. Log in as User A and create a row.
  2. Log in as User B in a private window. User B should not see or edit User A's row, and inserting a row with User A's user_id should fail.
  3. Log out and try again. Nothing private should load.

Our guide Is Lovable secure? has a fuller 20-minute version of this test.

FAQ

What is Supabase error 42501? It is Postgres's "insufficient privilege" code. With Supabase it almost always means an RLS policy (or a missing one) blocked the request.

Can I just use the service role key on the server? Yes, in trusted server code such as an Edge Function, as long as it never reaches the browser. But then your code must do the permission checks RLS would have done, so for normal user actions, policies are safer.

Lovable keeps "fixing" this by disabling RLS. What should I do? Tell it explicitly to keep RLS on and add the missing insert and select policies for the table, using auth.uid(). Then run the two-account test above yourself.


Not sure your policies are right? Our free Launch Check looks at your live app with your permission and shows the top risks we can see, and the free Launch Readiness Checklist scores the rest. If you want it fixed, see how we fix Supabase apps and Lovable apps, or try our pricing calculator for an estimate.

Sources: Supabase Row Level Security docs, Supabase Storage 403 troubleshooting, Matt Palmer on CVE-2025-48757. Checked September 2026.

Rather get it by email?

Leave your name and email, plus your app link if you like. We reply with a free Launch Check, no obligation.

We email you about this request only, and you can reply “unsubscribe” at any time. Prefer email? Write to info@pixelridgeweb.com. See our privacy notice.

Need it fixed for you? Lovable app fix and security audit or Supabase RLS audit: row level security and key review.

Want a second pair of eyes on your app?

Send us your live link. We record a short video showing the top risks we can see, free and with no obligation.

Terms and conditions apply. Read the terms.