new row violates row-level security policy for table "<your_table>"
Filed under Supabase & databases
The fix, fast
This error means Row Level Security is on for your table but no policy allows the insert you just tried, so Postgres blocks the row. Add an INSERT policy with a WITH CHECK clause — for user-owned rows that is usually WITH CHECK (auth.uid() = user_id) — and make sure the request is actually signed in, so auth.uid() is not null. Do not fix it by turning RLS off.
Why this happens
Row Level Security, usually shortened to RLS, is a Postgres feature Supabase turns into your safety net. When RLS is enabled on a table, the default is deny everything: no one can read or write any row unless a policy — a written rule on the table — says they can. That surprises a lot of vibe coders because the table looks wide open in the dashboard, yet every insert from the app fails.
The other trap is how policies are written. A SELECT policy uses a USING clause, which decides which existing rows someone can see. An INSERT policy needs a WITH CHECK clause, which decides whether the new row is allowed in. If you only wrote a read policy, or you wrote an insert rule with USING instead of WITH CHECK, inserts still fail. Finally, most policies check auth.uid() — the ID of the currently signed-in user. If your app is using the anon key, the public key you ship in browser code, without a signed-in session behind it, auth.uid() is null and a check like auth.uid() = user_id can never pass.
- RLS on, no INSERT policy at all: the most common cause, especially on a new table.
- USING vs WITH CHECK mix-up: your policy covers reads, not the new row being inserted.
- Not signed in: the request is anonymous, so
auth.uid()is null. - Key confusion: the service role key bypasses RLS and belongs only on a trusted server; the anon key does not bypass it.
The fix, step by step
- Confirm RLS is on and see what policies exist. In the Supabase dashboard, open your project, go to Authentication > Policies or Table Editor > select the table > View policies, and check whether any policy covers INSERT. In the SQL Editor you can also run the query below. If there is no INSERT policy, that is your answer — continue to step 2.
select policyname, cmd, qual, with_check from pg_policies where tablename = 'your_table'; - Add an INSERT policy with WITH CHECK. For the common pattern where each row belongs to one user and the table has a
user_idcolumn, allow users to insert only rows stamped with their own ID. Run this in the SQL Editor, replacing the table and column names with yours.alter table public.your_table enable row level security; create policy "Users can insert their own rows" on public.your_table for insert to authenticated with check (auth.uid() = user_id); - Add the matching SELECT policy if reads also fail. Inserts and reads are separate permissions. If users should also see their own rows after inserting them, add a SELECT policy with USING. Many apps need both, plus separate policies for UPDATE and DELETE if you use those operations.
create policy "Users can read their own rows" on public.your_table for select to authenticated using (auth.uid() = user_id); - Make sure the app is actually signed in and sends the right user_id. Check that a user session exists before the insert, and that the object you insert sets
user_idto that signed-in user's ID. If you are testing while logged out, or the insert leavesuser_idempty or sets someone else's ID, WITH CHECK will correctly reject it.const { data: { user } } = await supabase.auth.getUser(); if (!user) throw new Error("You must be signed in first."); const { error } = await supabase .from("your_table") .insert({ title: "My first row", user_id: user.id }); - Test the policy as a real user in the SQL Editor. The SQL Editor normally runs with elevated privileges, so a passing test there can lie. Use role simulation: in the SQL Editor, use the role selector to run as an authenticated user, or run a transaction that sets the role and claims. Simpler and often clearer: test from your app while signed in as a real test user, and try both an allowed row and a row with a different user_id — the second one should fail.
- Do not disable RLS to make it pass. Turning RLS off makes the error vanish because the table becomes readable and writable by anyone who has your anon key — and that key is visible to anyone who opens your site, by design. That means strangers could read or delete every row. If a tutorial tells you to disable RLS or use the service role key in browser code to get unstuck, treat that as a temporary local debug step at most, never the shipped fix.
Give your AI this prompt
I am using Supabase and getting Postgres error 42501: new row violates row-level security policy for table "[paste table name]" when I try to insert from my app. Here is my insert code and the relevant table columns: [paste code and note which column stores the owner, e.g. user_id]. Please: 1) explain whether I am missing an INSERT policy, 2) write the CREATE POLICY SQL I need using WITH CHECK (auth.uid() = user_id) for inserts and USING for selects if needed, 3) check my code for a missing signed-in session or a user_id that does not match the signed-in user, 4) tell me how to test it as an authenticated user. Constraints: do not suggest disabling Row Level Security, do not suggest putting the service role key in client/browser code, make one change at a time, and do not rewrite unrelated files or tables.