Guides by TICWays to earnPlatformsReality checkFree toolsRecipe Pack

Answers

new row violates row-level security policy (USING expression) for table "settings"

checked 2026-10-09

Short answer. An upsert that works the first time and fails the second needs an update policy and a matching select policy. The words (USING expression) in the message are the clue. Add an update policy with the same owner condition for using and with check, next to the insert one. Tested on 2026-10-09.

By Tori, last checked 2026-10-09. Every source is linked and was read on the date shown.

SQLSTATE 42501. Note the extra words in brackets: (USING expression). They are the clue. The shorter message without them, covered on the insert page, points at an insert policy. This one points at an update (or select) policy.

What causes it, in plain words

An upsert (insert ... on conflict do update, supabase-js .upsert()) is two operations behind one call. If the row does not exist, it is an insert and needs an insert policy. If the row exists, it turns into an update, and needs an update policy plus a select policy that lets you see that row. You wrote an insert policy, tested with a new row, and it worked. The first save of every user works; the second one fails.

The fix

Add the update policy next to the insert one. Use the same owner condition for using (which existing rows may be changed) and with check (what the changed row may look like):

create policy s_upd on public.settings
  for update to authenticated
  using (auth.uid() = user_id)
  with check (auth.uid() = user_id);

You also need a select policy on the same condition (the test table had one). Supabase's guide says an update needs a matching select policy, so do not skip it. I did not test an upsert with the select policy removed.

Evidence: tested

Tested on 2026-10-09 on a throwaway local PostgreSQL 16.15 cluster with stand-in roles (plain PostgreSQL, not a live Supabase project). Table settings (user_id uuid primary key, theme text), insert and select policies for authenticated, no update policy.

Case Result
First upsert, row does not exist succeeds (insert path)
Second upsert, row exists, no update policy ERROR 42501 new row violates row-level security policy (USING expression) for table "settings"; theme stays "dark"
Update policy added, same upsert succeeds, theme becomes "light"
Plain update of another user's row, with the update policy no error, no rows changed (the policy hides the row)

The last row is also why "my update runs but nothing changes" is not an error message: RLS filters rows out silently instead of complaining. Check before running on production: the new policy lets each user change their own row, so confirm which columns you are happy for them to edit.

Official docs

Supabase, "Row Level Security" guide: https://supabase.com/docs/guides/database/postgres/row-level-security (read 2026-10-09). It explains the using clause as deciding which existing rows an update can touch and the with check clause as deciding what the resulting row may look like, and says an update requires a corresponding select policy.

One free check

If one table had an insert policy and no update policy, others may too. TIC's free RLS auditor is a read-only SQL file you run yourself in your SQL editor and read the findings. It is a quick preflight, not a full audit: https://ticassociation.com/supabase-rls-audit?utm_source=agent-c13&utm_medium=guide&utm_campaign=supabase-upsert-new-row-violates-row-level-security-policy-using-expression

Questions

Why does my Supabase upsert fail the second time?
On the first save it is an insert. On the second the row exists, so it becomes an update, which needs an update policy.

What does (USING expression) in the error mean?
It points at an update or select policy, not at the insert policy covered on the insert page.

Do I also need a select policy for an update?
Supabase's guide says an update needs a matching select policy, so add one with the same condition.

What is the SQLSTATE code?
42501.

Keep reading