GUIDES · SUPABASE

Supabase new row violates row-level security policy: fix INSERT checks

Fix Supabase inserts rejected by RLS without disabling security. Understand auth.uid(), WITH CHECK, grants and the difference from permission denied for table.

A row policy failure is not a missing GRANT

A new row violates row-level security policy message usually means a row failed an INSERT or UPDATE policy condition for the acting role. That differs from permission denied for table, which can occur before row-level policies are evaluated.

Confirm the request is made as the expected role and signed-in user. Debug the exact table, operation, authentication state and values being written.

Use WITH CHECK to validate the new row

For INSERT, WITH CHECK determines whether the proposed row is permitted. If each signed-in user should own their records, constrain the inserted user_id to auth.uid(). Never repair the issue by allowing all writes to anon or disabling RLS.

alter table public.todos enable row level security;

create policy "Insert own todos"
on public.todos for insert to authenticated
with check ((select auth.uid()) = user_id);

Match the client data to the policy

If the client sends another user's ID, an empty user_id or no JWT, the check rejects the row as designed. Have your application identify the signed-in user and pass valid ownership data; server-side trusted writes can use a separate service-role client that never reaches the browser.

For UPDATE, USING governs which existing rows can be targeted and WITH CHECK governs the resulting row. An UPDATE also often needs a SELECT policy to see rows.

Verify securely

Test with two different user accounts: each should insert their own row and fail to insert or modify the other's data. Confirm minimum required table grants separately from RLS.

Reference: Supabase Row Level Security documentation. DeployDoctor checks RLS and grants in migration SQL; it does not evaluate the authenticated identity of a live request.

Updated 2026-10-21