aiRepoFix / How-to fixes
Supabase returns an empty array, or says "new row violates row-level security policy": how to fix it
Last checked against vendor documentation: October 17, 2026.
Short answer: row level security (RLS) is on, and no policy allows what your app is trying to do. With RLS on and no matching policy, Postgres denies by default: reads quietly return nothing, inserts fail with an error, and updates and deletes quietly change zero rows. Add a policy for the right operation and role. Don't turn RLS off to make it go away.
How to tell what you are seeing
| What you see | Usually means |
|---|---|
A read returns [] or no rows, with no error, but the table has data | RLS is filtering the rows out. No policy matches this user and operation. |
An insert fails with new row violates row-level security policy for table "your_table" (code 42501). An update that changes a row into something your policy forbids (for example a different user_id) fails the same way | RLS is on and there is no insert policy, or the new row doesn't satisfy the policy's with check rule. |
| An update or delete "works" but changes nothing: no error, zero rows affected, and the data is unchanged | No update (or delete) policy matches this user, or no select policy lets the update see the row. Postgres filters the rows out silently. Tip: with supabase-js, add .select() after the filters so the call returns the rows it changed, and treat an empty result as "nothing changed". |
permission denied for table your_table (code 42501) | A different problem: a missing grant, which is checked before RLS. See our page on 42501 and grants. |
Supabase's API keys guide describes this split: a missing grant returns a permission error, whereas a policy that matches no rows returns an empty result. Supabase's RLS guide says the same about a using rule that filters rows out: it raises nothing and matches zero rows. Check the grant first, then the policy.
Why it happens
The PostgreSQL docs say that when RLS is enabled on a table, all access must be allowed by a policy, and if no policy exists a default-deny policy applies, so no rows are visible or modifiable. Supabase says the same: once RLS is enabled, no data is accessible through the API with a publishable key until you create policies.
Also keep in mind:
- Filtering is silent for reads, updates and deletes. Rows that fail a policy's
usingrule are simply not returned, updated or deleted, and no error is raised. Your app can't tell "no rows" from "rows I'm not allowed to see". Only inserts, and updates that break awith checkrule, raise an error. We confirmed this on a local PostgreSQL test database: with RLS on and no policies, a select returned nothing, an insert raised 42501, and an update and a delete each affected zero rows with no error. - Your SQL Editor test may look fine. Supabase notes that its
postgresrole hasbypassrls, and the PostgreSQL docs say such roles bypass RLS. The SQL Editor typically runs with elevated rights, so a query can show rows there and nothing in your app. - Not signed in means no match.
auth.uid()isnullwhen no user is signed in, soauth.uid() = user_idnever matches. A policy writtento authenticatedalso doesn't apply to signed-out requests. - Updates need a read policy too. Supabase warns that an
UPDATEneeds a matchingSELECTpolicy to work as expected.
Find the missing policy
- List the policies on the table in the SQL Editor:
Look for a policy whoseselect policyname, cmd, roles, qual, with_check from pg_policies where tablename = 'your_table';cmdmatches what you're doing (SELECT,INSERT,UPDATE,DELETE, orALL) and whoserolesincludes the role your request uses (anonfor signed-out users,authenticatedfor signed-in users). - Confirm the user is actually signed in when the request runs. If your app calls the database before the session has loaded, it will run as
anon. - Test as that user inside a transaction (nothing is saved):
An empty result here, while the same query returns rows asbegin; set local role authenticated; set local request.jwt.claim.sub = 'PASTE-A-USER-UUID-HERE'; select * from public.your_table; rollback;postgres, confirms the policy is the cause. Supabase's guide uses the sameset local roleandrequest.jwt.claim.subapproach for tests. - Compare the policy rule with your data. For example, if the rule is
auth.uid() = user_id, check theuser_idcolumn is filled in on your rows and matches the signed-in user's id. Inserts that leaveuser_idempty fail the check.
Fix: one policy per operation
This example fits a table where each signed-in user manages their own rows. Adjust the table and column names. Supabase recommends writing a separate policy for each operation and always naming the role with to:
alter table public.your_table enable row level security;
create policy "read own rows" on public.your_table
for select to authenticated
using ((select auth.uid()) = user_id);
create policy "insert own rows" on public.your_table
for insert to authenticated
with check ((select auth.uid()) = user_id);
create policy "update own rows" on public.your_table
for update to authenticated
using ((select auth.uid()) = user_id)
with check ((select auth.uid()) = user_id);
create policy "delete own rows" on public.your_table
for delete to authenticated
using ((select auth.uid()) = user_id);
- The table also needs grants for
authenticatedfor the operations you use (see our 42501 page). - For data that really is public (say, a list of published posts), Supabase's guide shows a read-only policy
to anon, authenticated using (true). Use that only for data that is meant to be public. - Wrapping
auth.uid()as(select auth.uid())is Supabase's recommended form for performance. Adding an index on the column the policy filters on (likeuser_id) also helps as the table grows.
Common mistakes
- Disabling RLS. It makes the error disappear and makes every row reachable by anyone who has a grant on the table. Supabase warns that a table in an exposed schema without RLS is readable and writable by any role with a grant on it.
- Writing
using (true)foranonon private data. That lets every visitor read every row. - Using the secret /
service_rolekey in the browser to get around it. Supabase says this key bypasses RLS and must never be in a browser or source control. - Assuming no error means it worked. An update or delete blocked by RLS reports success with zero rows changed. Check what came back, or read the row again.
- A policy for the wrong operation. An
insertpolicy does not let you read rows back, and ato authenticatedpolicy does nothing for signed-out requests. - Creating policies only in the dashboard. Put policies in your migration files so they exist in every environment.
- Views. Supabase notes views can bypass RLS because they're usually created by the
postgresuser. On Postgres 15+, create them withsecurity_invoker = trueif they should respect the table's policies.
What you can do yourself, and when it is a code change. For one table, the policies above are a few minutes of SQL in the Supabase SQL Editor. It becomes a code change when the policies depend on teams, roles or shared data (where wrong policies can expose data or lock users out), when the app queries before the session loads, or when policies need to live in migrations across several tables. We work from your GitHub repo and deliver a pull request. We can't change settings in your Supabase dashboard; if a fix needs one, we say so in the pull request notes.
Sources
This page is based on the vendor documentation below. Menu names and defaults change, so check the source if something on your screen looks different.
- Supabase docs: Row Level Security
- Supabase docs: API keys (grants vs. policies, secret keys)
- Supabase docs: Securing your API
- Supabase JavaScript reference: update (return changed rows with select)
- Supabase troubleshooting: Database API 42501 errors
- PostgreSQL documentation: Row Security Policies
Last checked: October 17, 2026. aiRepoFix is not affiliated with or endorsed by the companies named on this page.