Skip to content

Supabase RLS errorSupabase: “infinite recursion detected in policy for relation”

A table that worked yesterday now fails for every user with infinite recursion detected in policy for relation, Postgres error 42P17. A row-level security policy is reading a table whose own policy leads back to it. AI tools often write these when adding teams or sharing. The quick fixes people paste can open the table to everyone, so here is the safe fix, in Supabase’s words, or hand it over at a fixed price.

Short answer

A policy on the table queries a table with RLS on, and that table’s policy ends up running the first policy again. Move the lookup into a security definer function in a private schema, with search_path set to an empty string, and call it from both policies. Don’t fix it by turning RLS off or by writing a policy that lets everyone read.

Why the policies loop, and the safe fix

  1. 01

    A policy queries its own table, or a table that queries it back

    “It occurs when an RLS policy queries a table with RLS enabled, and that table's policies end up triggering the original policy again.” Supabase, Infinite recursion detected in policy for relation

    Look at the policies on the table named in the error. A policy on profiles that checks profiles to find the user’s role loops on itself. Two tables whose policies check each other loop too, such as projects checking project_members while project_members checks projects.

  2. 02

    Sharing and team features are the usual shape

    “Two tables whose policies read each other never resolve.” Supabase, Row Level Security

    The table that says who belongs to a team, list or workspace is where the loop forms: its policy checks the owner on the main table, and the main table’s policy checks the members.

  3. 03

    Break the loop with a security definer function

    “It reads the membership table as its owner, so the second policy never runs and the cycle is broken” Supabase, Row Level Security

    Put the membership lookup in a function that runs as its owner, so it reads the table without running that table’s policy. Create it as postgres in the SQL editor, rewrite both policies to call it, and drop the old policies that caused the loop. This is the shape of Supabase’s example, adapted to a team table:

    create schema if not exists private;
    
    create function private.user_team_ids()
    returns setof uuid
    language sql
    security definer
    set search_path = ''
    stable
    as $$
      select team_id from public.team_members
      where user_id = (select auth.uid())
    $$;
    
    revoke execute on function private.user_team_ids() from public;
    grant usage on schema private to authenticated;
    grant execute on function private.user_team_ids() to authenticated;
    
    create policy "members read teams" on teams for select
    to authenticated
    using ( id in (select private.user_team_ids()) );
    
    create policy "members read memberships" on team_members for select
    to authenticated
    using ( team_id in (select private.user_team_ids()) );
  4. 04

    Keep the function out of the API

    “Never create one in a schema listed under "Exposed schemas" in your API settings.” Supabase, Row Level Security

    A security definer function runs with its owner’s rights, so where it lives matters. Create it in a schema that isn’t exposed, such as private, pin search_path, and filter on auth.uid() inside it so it only returns the caller’s rows.

  5. 05

    Test it as real users, not in the SQL editor

    The SQL editor runs as postgres, which skips RLS, so a policy can look fixed there and still fail in the app. We test as a signed-in member, a non-member and a signed-out visitor, and check each sees only what they should.

Checked against the vendor’s docs on : Supabase troubleshooting: Infinite recursion detected in policy for relation; Supabase docs: Row Level Security (avoid recursive policies).

Rather hand it over?

Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.

Related: Supabase new row violates row-level security · Supabase RLS enabled but data still exposed · Supabase select returns an empty array · Supabase Signups not allowed for this instance

Questions

More on this error

Can I just disable RLS on the table to stop the error?

It stops the error and makes every row readable with your public key. Fix the policies instead. If you need the app working right away, take the feature that uses the table offline rather than opening the data.

Why does it also break Storage uploads?

Supabase says this error can also occur during Storage operations if a Storage RLS policy references a table with self-referencing or mutually recursive RLS policies. The fix is the same: break the loop in that table’s policies.

Can you fix it for me?

If one bug is the whole problem, the App Fix Ticket covers it: a written diagnosis first, then the fix. If the app has more going on, the Launch Check is a written check of the whole app. Each issue found is rated critical, high, medium or low, with where it is and how it was confirmed. No issues worth fixing, no charge. If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way.

A different error?

Describe it and get a written quote

Send the error and what you were doing. You get a written scope, a fixed price and a timeline; we reply by email, usually within one business day.