Blog
3 min read

"new row violates row-level security policy" in Supabase: How to Fix It

Supabase (Postgres) blocked an insert or update because no RLS policy allows it. Why it happens, how to write an INSERT policy with WITH CHECK, the user_id default trick, the hidden SELECT-after-insert problem, storage uploads, and why you must never 'fix' it by disabling RLS.

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

Code 42501. Your Supabase table has row-level security (RLS) enabled, and no policy allows this user to insert (or update) this row. (Supabase RLS explained)

This is RLS working. With RLS on, everything is denied by default; policies grant specific access. The fix is to add the right policy — not to turn RLS off.

Never "fix" it by disabling RLS

AI tools sometimes suggest ALTER TABLE todos DISABLE ROW LEVEL SECURITY. With Supabase, your frontend talks to the database directly using a public key. Without RLS, anyone can read and modify every row in that table — other users' data included. (Security checklist for vibe-coded apps)

Fix 1: add an INSERT policy

For a table where each row belongs to a user:

CREATE POLICY "Users can insert their own todos"
ON todos
FOR INSERT
TO authenticated
WITH CHECK ( (SELECT auth.uid()) = user_id );
  • FOR INSERT — this policy covers inserts.
  • TO authenticated — only logged-in users.
  • WITH CHECK — the new row must satisfy this. Here: its user_id must be the logged-in user.

For updates you need both USING (which existing rows can be targeted) and WITH CHECK (what they may become):

CREATE POLICY "Users can update their own todos"
ON todos FOR UPDATE TO authenticated
USING ( (SELECT auth.uid()) = user_id )
WITH CHECK ( (SELECT auth.uid()) = user_id );

Fix 2: make sure user_id is actually set

The policy compares user_id to the current user. If your insert doesn't include user_id, it's NULL, the check fails, and you get this error.

Either send it from the frontend:

const { data: { user } } = await supabase.auth.getUser()
await supabase.from('todos').insert({ title, user_id: user.id })

…or, better, set it automatically with a column default so the client can't get it wrong:

ALTER TABLE todos ALTER COLUMN user_id SET DEFAULT auth.uid();

Fix 3: check the user is really logged in

If the request is made without a valid session, it runs as the anon role and auth.uid() is NULL. Policies for authenticated won't apply. Check the session exists before inserting, and that your server-side code passes the user's token rather than using the bare anon key. (401 vs 403)

Fix 4: the hidden SELECT problem

This catches many people:

await supabase.from('todos').insert({ title }).select()

The .select() asks for the inserted row back. That requires a SELECT policy too. With an insert policy but no select policy, the insert can fail with the same RLS error. Add:

CREATE POLICY "Users can read their own todos"
ON todos FOR SELECT TO authenticated
USING ( (SELECT auth.uid()) = user_id );

Fix 5: storage uploads

The same message appears for file uploads to Supabase Storage — storage uses RLS policies on the storage.objects table. Add an INSERT policy for the bucket, usually restricting uploads to a folder named after the user's ID. (Where should user uploads go?)

Server-side code with the secret key

Code running on your server (never in the browser) can use Supabase's secret (service role) key, which bypasses RLS entirely. That's appropriate for admin tasks and background jobs — but then your code is responsible for checking permissions. Never ship that key to the frontend. (Hide API keys)

Testing policies

In the Supabase SQL editor you can test as a specific user:

SET request.jwt.claims = '{"sub": "USER-UUID-HERE", "role": "authenticated"}';
SET ROLE authenticated;
INSERT INTO todos (title) VALUES ('test');
RESET ROLE;

Supabase's dashboard also flags tables with RLS disabled or no policies. (Supabase MCP can help an AI agent check them.)


EasySpawn runs your app with its own Postgres database and a server-side backend, so access rules can live in code you control — with Claude Code on hand to write and test RLS policies if you use them. See how it works or join the waitlist.

Related: Supabase Row-Level Security Explained · What Is Supabase? · Multi-Tenant SaaS on Postgres · A Security Checklist for Vibe-Coded Apps

Keep reading