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 seeUsually means
A read returns [] or no rows, with no error, but the table has dataRLS 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 wayRLS 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 unchangedNo 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:

Find the missing policy

  1. List the policies on the table in the SQL Editor:
    select policyname, cmd, roles, qual, with_check
    from pg_policies
    where tablename = 'your_table';
    Look for a policy whose cmd matches what you're doing (SELECT, INSERT, UPDATE, DELETE, or ALL) and whose roles includes the role your request uses (anon for signed-out users, authenticated for signed-in users).
  2. 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.
  3. Test as that user inside a transaction (nothing is saved):
    begin;
    set local role authenticated;
    set local request.jwt.claim.sub = 'PASTE-A-USER-UUID-HERE';
    select * from public.your_table;
    rollback;
    An empty result here, while the same query returns rows as postgres, confirms the policy is the cause. Supabase's guide uses the same set local role and request.jwt.claim.sub approach for tests.
  4. Compare the policy rule with your data. For example, if the rule is auth.uid() = user_id, check the user_id column is filled in on your rows and matches the signed-in user's id. Inserts that leave user_id empty 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);

Common mistakes

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.

Last checked: October 17, 2026. aiRepoFix is not affiliated with or endorsed by the companies named on this page.

Still stuck? Describe it free.

Tell us what is broken. We review it and either email you the flat price for the tier that fits ($49 Quick, $99 Standard, $149 Full) with a payment link, or tell you we can't take it, and you pay nothing. The fix comes as a pull request you review, and nothing in your app changes until you merge it. Pull request delivered within 96 hours of a complete order (weekends and US holidays don't count), or 20% back. (conditions apply)

Please don't send passwords, API keys or .env files. We don't need them.

aiRepoFix is an independent service that fixes bugs in the code in your own GitHub repository and delivers a pull request; it is not technical support for Lovable, Supabase, Cursor, Bolt, Replit or any other platform mentioned on this page.

Still stuck? Describe it free →