infinite recursion detected in policy for relation "members"
checked 2026-10-09
By Tori, last checked 2026-10-09. Every source is linked and was read on the date shown.
SQLSTATE 42P17. Your table name replaces members. Typical in team or organisation apps where a user may see rows of everyone in the same team.
What causes it, in plain words
To decide whether you may see a row of members, the policy looks up your rows in members. That lookup is itself subject to the policy, which looks up members, and so on. Postgres notices the loop and stops with this error.
The classic way to write it:
-- loops
create policy members_sel on public.members for select to authenticated
using (org_id in (select org_id from public.members where user_id = auth.uid()));
The fix
Move the lookup into a function that runs with its owner's rights, so reading members inside it does not trigger the policy again. Then the policy calls the function:
create function public.my_org_ids()
returns setof int
language sql stable security definer
set search_path = ''
as $$ select org_id from public.members where user_id = auth.uid() $$;
revoke all on function public.my_org_ids() from public;
grant execute on function public.my_org_ids() to authenticated;
create policy members_sel on public.members for select to authenticated
using (org_id in (select public.my_org_ids()));
Three cautions, all from Supabase's RLS guide (read 2026-10-09), which also describes this pattern:
- Set
search_pathto empty and write every name with its schema, as above, so a caller cannot redirect the lookups. - A security definer function can be called through the API if it sits in an exposed schema, with its creator's rights. Consider a schema that is not exposed, and revoke execute from roles that do not need it.
- The loop can come back if the function's owner does not bypass RLS or the table has forced RLS. On Supabase the
postgresowner bypasses RLS; on your own Postgres, check who owns the function.
Evidence: tested
Tested on 2026-10-09 on a throwaway local PostgreSQL 16.15 cluster with stand-in roles (plain PostgreSQL, not a live Supabase project). Three users, two in organisation 1 and one in organisation 2.
| Case | Result |
|---|---|
Policy reads members inside its own using clause, select as user 1 |
ERROR 42P17 infinite recursion detected in policy for relation "members" |
| Policy replaced with the security definer helper, same select | 2 rows, both from organisation 1, none from organisation 2 |
The function owner in the test was the table owner, which is the situation that bypasses RLS. If you copy this onto a table with forced RLS, check before running.
Official docs
Supabase, "Row Level Security" guide: https://supabase.com/docs/guides/database/postgres/row-level-security (read 2026-10-09), section on security definer functions. Supabase also keeps a troubleshooting entry titled "RLS policy causes infinite recursion" in its troubleshooting index (https://supabase.com/docs/guides/troubleshooting, index read 2026-10-09; I read the title, not the entry body).
One free check
A security definer function is a deliberate hole in the wall, so it should be checked after you write it. TIC's free RLS auditor is a read-only SQL file you run in your own SQL editor. Whether it reports on security definer functions is not something I verified for this page, so read its output for what it does cover: https://ticassociation.com/supabase-rls-audit?utm_source=agent-c13&utm_medium=guide&utm_campaign=supabase-infinite-recursion-detected-in-policy-for-relation
Questions
What causes infinite recursion detected in policy?
A policy on a table that reads the same table to decide access. The lookup is itself subject to the policy, so Postgres stops with this error.
What is the SQLSTATE code for this error?
42P17.
How do I fix it without turning RLS off?
Move the lookup into a function that runs with its owner's rights and have the policy call that function.
Is a security definer function safe?
Only if you set its search_path and limit who can run it. The page shows the cautions that go with it.