BuiltSafely

Home/Fixes

Supabase

new row violates row-level security policy (Supabase)

Your insert or update is blocked by RLS. Add a policy for that exact write, without turning RLS off.

  • Reviewed Oct 2026
  • 3 official sources
Sources Last checked October 16, 2026 · Sources: official docs (linked below) · Educational, not an audit

The fix

Add a policy that allows this exact write for the role making the request, and make sure the row you send passes it. In most apps, that means:

  1. There's an insert (or update) policy for authenticated.
  2. The row's user_id matches the signed-in user.
  3. The user really is signed in when the request runs.
  4. If your code asks for the row back (for example .insert(...).select()), there's also a select policy that covers the new row.
create policy "notes insert own" on public.notes
  for insert to authenticated
  with check ( (select auth.uid()) = user_id );

create policy "notes select own" on public.notes
  for select to authenticated
  using ( (select auth.uid()) = user_id );

Tested October 16, 2026 against a throwaway local Supabase Postgres 17 container (no real project). Without the insert policy, the insert failed with this error. With only the insert policy, a plain insert worked, but an insert that asked for the row back (returning) still failed. With both policies, it returned the new row.

Why it happens

With RLS on, Postgres refuses anything no policy allows. Supabase notes that once RLS is enabled, no data is reachable through the API with a publishable key "until you create policies" (Supabase: RLS). For inserts, the with check expression decides whether the new row is allowed, and a failed check raises error 42501 (Supabase: RLS).

The common causes:

  • No policy for this operation or role. A select policy doesn't cover inserts. Supabase recommends one policy per operation (Supabase: RLS).
  • The row doesn't match. The app sends no user_id, or a different one. A column default of auth.uid() helps.
  • No session. Without a signed-in user, auth.uid() returns null, so ownership checks fail (Supabase: RLS).
  • Asking for the row back. Supabase's troubleshooting note explains that an insert followed by RETURNING fails if no select policy covers the new row (Supabase troubleshooting).

What not to do

Don't disable RLS, and don't replace the policy with with check (true) on private data. Don't switch to the secret or service_role key in the browser either. It bypasses RLS and must stay server-side (Supabase: API keys).

If the message says "permission denied for table" instead, the problem is a missing grant, not a policy. See our 42501 fix.

Go deeper: our Supabase RLS guide.

Your turn

Which of the four causes was it for you? Tell us. It helps us decide what to explain better.

This guide is educational and based on the vendor's public docs as of the review date. Platforms change: check the linked sources. Not a security audit. Disclaimer.

The BuiltSafely weekly

One email a week. What changed, and the one fix that matters.

Platform changes that affect the security of AI-built apps, checked against official docs. Free. Unsubscribe any time.