"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: itsuser_idmust 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
The Supabase MCP Server: Let AI Work With Your Database (Safely)
Supabase's MCP server lets Claude Code, Cursor and other AI tools inspect your tables, run SQL, apply migrations and read logs. How to connect it, why you should scope it to one project in read-only mode, and the risks of giving an AI database access.
Supabase Row-Level Security Explained (for People Who Didn't Write the Policies)
If your app talks to Supabase from the browser, row-level security is the only thing standing between your users' data and anyone who opens the developer tools. What RLS is, how to read the policies your AI tool wrote, the four mistakes that leave data exposed, and how to test it yourself.