What This Supabase Error Means

You call insert() or upload a file, and Supabase returns:

new row violates row-level security policy for table "todos"

(Postgres error code 42501.)

This isn't a bug in Supabase. Row Level Security (RLS) is on, and no policy allows this specific write. Postgres checks every new row against your table's INSERT (or UPDATE) policies. If none returns true for the current user, the write is rejected.

The fix is almost always one of five things. Work through them in order.


🎯 Quick Answer (30-Second Read)

  • Cause: RLS is enabled and no INSERT/UPDATE policy permits this row for this user.
  • Fix 1: add an INSERT policy with a WITH CHECK condition (for example auth.uid() = user_id).
  • Fix 2: make sure the request is authenticated, since a logged-out client runs as anon.
  • Fix 3: send the column the policy checks (often user_id) with the right value.
  • Fix 4: for file uploads, add a policy on storage.objects, not your own table.
  • Don't disable RLS or ship the service-role key to the browser.

Fix Checklist (Try in This Order)

  • 1. Does an INSERT policy exist for this table and role?
  • 2. Is the user actually logged in when the request runs?
  • 3. Does the inserted row include the column your policy checks?
  • 4. If upserting or updating: is there also an UPDATE (and SELECT) policy?
  • 5. If uploading a file: is there a policy on storage.objects?

Fix 1: Add an INSERT Policy With WITH CHECK

INSERT policies use WITH CHECK (they check the new row). USING only filters existing rows. A policy written only with USING won't allow inserts.

-- Allow logged-in users to insert rows they own
create policy "Users can insert their own todos"
on public.todos
for insert
to authenticated
with check ( (select auth.uid()) = user_id );

Matching policies for reading and editing:

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 );
Pro tip: wrapping auth.uid() in (select auth.uid()) is Supabase's recommended pattern. Postgres evaluates it once per query instead of once per row, which keeps policies fast on large tables.

Fix 2: Make Sure the Request Is Authenticated

If the client isn't signed in, the request runs as the anon role and auth.uid() is null. Then auth.uid() = user_id is never true, and the insert fails.

Common causes:

  • The user's session expired or was never set.
  • In Next.js, the server-side Supabase client was created without the user's cookies, so the server acts as an anonymous user.
  • You're calling from a script or cron job with only the anon key.

Quick check before the insert:

const { data: { user } } = await supabase.auth.getUser();
console.log(user?.id); // must not be undefined

Fix 3: Send the Column the Policy Checks

If the policy checks user_id but your insert doesn't send it, the value is null (or a default) and the check fails.

const { data: { user } } = await supabase.auth.getUser();

const { error } = await supabase
  .from("todos")
  .insert({ title: "Ship the blog post", user_id: user!.id });

Better option: make the database fill it in automatically, so the client can't get it wrong:

alter table public.todos
  alter column user_id set default auth.uid();

Fix 4: Upserts Need More Than an INSERT Policy

upsert() can update existing rows, so Postgres also checks your UPDATE policy. If you call .select() after writing, the returned rows also need a SELECT policy.

If inserts work but upserts fail, add the missing update and select policies from Fix 1.


Fix 5: File Uploads Need a Storage Policy

The same error appears when uploading to Supabase Storage, because Storage keeps file records in the storage.objects table, which has RLS enabled. Your own table's policies don't apply to it.

-- Let each user upload into their own folder: avatars//...
create policy "Users can 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
);

Upload to a matching path:

const { data: { user } } = await supabase.auth.getUser();
await supabase.storage
  .from("avatars")
  .upload(`${user!.id}/profile.png`, file);

What NOT to Do

Mistake: disabling RLS or using the service-role key in the browser "to make the error go away". The service-role key bypasses RLS entirely. Exposed in client code, anyone can read or delete every row in your database. Keep it on the server only.

How to Debug a Policy

  1. List the table's policies: Dashboard → Authentication → Policies, or run:
select policyname, cmd, roles, qual, with_check
from pg_policies
where tablename = 'todos';
  1. Check the cmd: you need one for INSERT (and UPDATE for upserts).
  2. Check the roles: a policy for authenticated won't apply to anon requests.
  3. Compare with_check with the row you're sending.

Frequently Asked Questions

Why does the insert work in the Supabase SQL editor but fail in my app?

The SQL editor runs as a privileged database role that isn't restricted by RLS policies. Your app runs as anon or authenticated, so policies apply. Test inserts from the client, or check your policies for the role your app actually uses.

What's the difference between USING and WITH CHECK?

USING decides which existing rows a user can see, update or delete. WITH CHECK decides whether a new or changed row is allowed. INSERT policies need WITH CHECK. UPDATE policies usually need both: USING for the old row and WITH CHECK for the new one.

Is it safe to disable RLS?

Only for tables that are never reachable from the client, and even then it's risky. Supabase exposes tables through its API with the public anon key, so a table without RLS can be readable or writable by anyone. Write the correct policy instead.


Conclusion

"New row violates row-level security policy" means RLS is working: no policy allowed that write. Add an INSERT policy with WITH CHECK, make sure the user is authenticated, send (or default) the owner column, add UPDATE/SELECT policies for upserts, and use storage.objects policies for uploads.


Sources

Last verified: 14 October 2026.


Related reads: How to Create a SaaS With Next.js and Supabase · How to Design a Database Schema for a SaaS App · How to Use Environment Variables in Next.js · PostgreSQL vs MySQL

I write about building products with Next.js, Supabase and AI. Follow on LinkedIn or X, or invite me to speak.