Guide
RLS blocks edits or upserts: add the SELECT policy too
Short answer: An UPDATE needs a matching SELECT policy on the same table, and an upsert that updates an existing row runs into the same rule. Add a SELECT policy for the same rows, then run the edit again.
Why it happens
This applies to apps built with Lovable (Cloud) and to any app that uses Supabase as its backend. Supabase's RLS guide states it plainly: to perform an UPDATE operation, a corresponding SELECT policy is required. Without a SELECT policy, the UPDATE operation will not work as expected.
A policy acts like an extra WHERE clause on every query. An UPDATE policy uses using to decide which existing rows can be updated, and with check to decide what the resulting row may look like. The row still has to be visible to the request, and visibility comes from a SELECT policy.
Upserts are affected too. upsert() performs the equivalent of insert() when no row matches the onConflict columns, and an update when one does. The update half is an UPDATE, so it needs the SELECT policy.
Two other things can produce a similar failure, so rule them out first:
- A missing grant raises a
42501error before any policy runs. When a request fails that your policy should allow, check the grants before you change the policy. - A
with checkviolation raises42501too. That one is about the values in the row, not about visibility.
How to fix it
- In Lovable, open
More → Cloud → Database → RLS policies. The view lists each policy with its name, its command (SELECT,INSERT,UPDATE,DELETE), who it applies to, and its rule expression. Filter by table to find the table your edit writes to. - Check whether that table has a policy whose command is
SELECTfor signed-in (authenticated) users. If it does not, that is your cause. - Open the SQL editor at
More → Cloud → SQL editorand add the missing policy. This is theSELECTpolicy pattern from Supabase's guide; change the table name and condition to match your table:create policy "Users can view their own profile." on profiles for select to authenticated using ( (select auth.uid()) = user_id );
- If you also need the update itself, write it as a separate policy. Postgres does not accept multiple operations in one
forclause:create policy "Users can update their own profile." on profiles for update to authenticated using ( (select auth.uid()) = user_id ) with check ( (select auth.uid()) = user_id );
- Always name the role with the
toclause, so the policy does not run foranonusers. - If the table is new and still carries automatic grants, set the grants to match what your app does. Revoke first, then grant back only what each role needs. This example is for a table that only signed-in users use; replace
reportswith your table and adjust the privileges:revoke all on table public.reports from anon, authenticated;grant select, insert, update, delete on table public.reports to authenticated;
- If your code uses
upsert(), make sure the primary key is included in the values. Supabase's upsert reference says primary keys must be included invaluesto use upsert. - If you use the Supabase CLI, add a test file for the table and run the suite:
supabase test new profiles_rls.testsupabase test db
How to check it worked on your live app
Sign in as the user who owns the row you were trying to change. Go to the screen that saves the edit or runs the upsert, and do the action that failed before. Then reload the page and confirm the change is still there. A using clause that filters the row out matches zero rows and raises nothing, so a save that looks fine but leaves the row unchanged is still a failure. If you want to test the other direction, sign in as a different user and confirm they cannot change that row.
Prompt to paste into your AI builder
Paste this into your AI builder: "My app saves to a Supabase table and the edit or upsert fails with a row-level security policy error. An UPDATE needs a matching SELECT policy on the same table. Show me the change first: list the existing RLS policies on that table with their command and rule expression, then write the SQL to add a SELECT policy for authenticated users that uses the same condition as the update policy, plus a separate update policy with using and with check. Name the role with to authenticated. Do not change any other table."
FAQ
Does an insert need a SELECT policy too? An insert policy uses with check on its own. But an insert that has to return the row it created needs a SELECT policy to return it; the upload case below is an example.
Why did my upload fail with the same message? Supabase's storage troubleshooting page says the Storage API runs an INSERT followed by RETURNING *. If a SELECT policy is missing or does not cover the object, the database cannot return the row metadata and the whole transaction fails. Add a SELECT policy that mirrors your INSERT requirements.
What is the difference between using and with check? using inspects the existing row to decide if it is visible to the query. with check inspects the values being added or modified. An update policy usually has both; if with check is left out, using decides both which rows are visible and which new rows are allowed.
My policy looks right but I still get 42501. A missing grant raises 42501 before any policy runs. Check the grants on the table before you change the policy.
Should I use one for all policy instead? Supabase recommends a separate policy for select, insert, update, and delete. A for all policy hides which operation each rule was meant to cover.
Related guides
- Data won't save: 'new row violates row-level security policy' covers the insert side of the same error.
- Supabase "permission denied for table" (42501): the fix explains the grant check that runs before any policy.
- Users can see other users' data in my Lovable app helps you confirm the new
SELECTpolicy is not too broad.
Sources
- Row Level Security | Supabase Docs
- JavaScript API Reference: upsert
- Storage error: 403 Forbidden: 'new row violates row-level security policy' on upload
- RLS Simplified
- Database - Lovable Documentation
VibeFix's AI agent signs up to your live app like a new person and shows whether sign-up and your pages work. It may not show update or upsert blocked by RLS without a SELECT policy directly. VibeFix checks; it does not fix your app.
Last checked: 18 October 2026, against the sources listed above.
VibeFix is independent. Lovable, Bolt, Replit, Base44, v0 and Supabase are trademarks of their owners.
Still stuck?
Paste your app’s link for a free sign-up check, no account needed. An AI agent signs up to your live app as a brand-new person, on a phone and a computer, and tells you whether a new person can finish.