Supabase RLS: infinite recursion in policy
Postgres raises this error (code 42P17) when checking a Row Level Security policy needs that same policy again. It happens when a policy reads its own table, or when two tables have policies that read each other. The query fails instead of returning rows. Team, membership, sharing and "is admin" checks are the usual cause.
Cause: An RLS policy queries a table that has RLS turned on, and that table's policies end up running the first policy again, so Postgres stops the loop.
How to fix it
- Note the table named in the error, then list its policies:
select policyname, qual, with_check from pg_policies where tablename = 'members'; - Find the policy whose
usingorwith checkselects from the same table, or from a table whose policies select back. - Move that lookup into a
security definerfunction in a schema that is not exposed through the API (for exampleprivate), withset search_path = ''and schema-qualified table names. - Revoke execute on the function from
publicand grant it only toauthenticated. - Rewrite the policy to call the function inside a select, for example
using (team_id in (select private.my_team_ids())). - Test as a member, a non-member and a signed-out user before you ship.
Example
Illustrative example, not from a customer project.
- create policy "members read their teams" on public.members for select to authenticated
- using (team_id in (select m.team_id from public.members m where m.user_id = (select auth.uid())));
+ create schema if not exists private;
+ create function private.my_team_ids() returns setof uuid
+ language sql stable security definer set search_path = ''
+ as $$ select team_id from public.members where user_id = (select auth.uid()) $$;
+ revoke execute on function private.my_team_ids() from public;
+ grant usage on schema private to authenticated;
+ grant execute on function private.my_team_ids() to authenticated;
+ create policy "members read their teams" on public.members for select to authenticated
+ using (team_id in (select private.my_team_ids()));
FAQ
Can I turn RLS off to make the error go away?
No. With RLS off, a table in the public schema can usually be read and changed by anyone who has your project's public key. Fix the policy instead.
Why do I see it on Storage uploads?
Supabase's troubleshooting guide notes it can happen when a Storage policy checks a table whose own policies are recursive. Fix the table's policies and the upload works again.
Is a security definer function safe?
Only if it lives in a schema that is not exposed through the API, pins search_path, and only returns rows for the signed-in user. It runs with its owner's rights, so keep it small.
Two tables check each other. Do I need two functions?
Usually one is enough. Supabase's guide breaks a lists and list_members loop with a single function that both policies call.
Get an auth & database security fix, from $400 Not sure? Get a $149 diagnosis
- Fixed, or we keep working free until it is (within the written scope).
- A human reviews every fix before it ships.
- Read-only access; fixes arrive as pull requests you merge.
- Your $149 diagnosis is credited in full toward any paid job.
Built with Lovable, Bolt, Replit, Cursor or v0? See rescue by platform and the security checklist.