Error library

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

  1. Note the table named in the error, then list its policies: select policyname, qual, with_check from pg_policies where tablename = 'members';
  2. Find the policy whose using or with check selects from the same table, or from a table whose policies select back.
  3. Move that lookup into a security definer function in a schema that is not exposed through the API (for example private), with set search_path = '' and schema-qualified table names.
  4. Revoke execute on the function from public and grant it only to authenticated.
  5. Rewrite the policy to call the function inside a select, for example using (team_id in (select private.my_team_ids())).
  6. 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

Built with Lovable, Bolt, Replit, Cursor or v0? See rescue by platform and the security checklist.

Check it yourself first, free

$149Get a $149 diagnosis