All guidesSupabase RLS and keys
On this page

Supabase Performance Advisor

Supabase

What does auth_rls_initplan mean, and how do I fix it?

By Ohad Krispin, founder of VibeRaven · LinkedIn

auth_rls_initplan is a Supabase Performance Advisor warning (lint 0003, level WARN), not a security one: a row level security policy calls auth.uid(), auth.jwt(), auth.role(), auth.email() or current_setting() directly, so Postgres may run the call once for every row it checks. The fix is to wrap the call in a select, as in (select auth.uid()) = user_id, so Postgres runs it once per query and reuses the result. The policy returns the same rows either way.

psql output from a Supabase Postgres 17.11 container with 200,002 rows: lint 0003 flags a policy that calls auth.uid() directly and a count takes 133 ms. With (select auth.uid()), the lint is empty and it takes 9.8 ms.
psql output from a Supabase Postgres 17.11 container with 200,002 rows: lint 0003 flags a policy that calls auth.uid() directly and a count takes 133 ms. With (select auth.uid()), the lint is empty and it takes 9.8 ms.

Checklist

How to fix auth_rls_initplan, then check the fix

  1. 01
    Read the policies on the table the Advisor names. pg_policies prints a wrapped call as ( SELECT auth.uid() AS uid), so the ones to change show a bare auth.uid():
    select policyname, cmd, qual, with_check
    from pg_policies
    where schemaname = 'public' and tablename = 'notes';
  2. 02
    Rewrite each one with alter policy, which keeps its command and roles. Wrap the call in using for reads, updates and deletes, and in with check for inserts and updates:
    alter policy "Users read their own notes" on public.notes
    using ( (select auth.uid()) = user_id );
    
    alter policy "Admins add notes" on public.notes
    with check ( ((select auth.jwt()) -> 'app_metadata' ->> 'role') = 'admin' );
  3. 03
    Run the Performance Advisor again. supabase db advisors --type security leaves this lint out; on Supabase CLI 2.121.0 it showed only with --type performance or all:
    supabase db advisors --linked --type performance
  4. 04Time a query before and after. On a Supabase Postgres 17.11 container on 22 October 2026, with 200,002 rows and a user who owns 1, select count(*) took 113 to 133 ms over three runs with auth.uid() = user_id, and 8.8 to 9.8 ms with (select auth.uid()) = user_id. Both returned 1.

Exceptions

When auth_rls_initplan can wait

The lint's source flags any policy, on a table with RLS on, whose using or with check text contains one of those calls without select in front of it.

Small tables

The cost grows with the rows a query checks, not with the rows it returns. A table of a few hundred rows will not show the difference, though the fix is one line and changes nothing else.

Indexes, the other half

Supabase's RLS guide also recommends an index on each column a policy compares. Wrapping the call fixes the lint; the index stops the full table scan.

create index on public.notes (user_id);

Related

Sources

Official docs behind this checklist

FAQ

FAQ for builders before launch

Does wrapping auth.uid() in select change which rows a user sees?

No. Supabase's lint docs say the helpers return the same value for the whole query, so caching it changes only how often it runs. On the test container both versions returned the same count.

Is auth_rls_initplan a security warning?

No. The lint's category is PERFORMANCE, and the Advisor lists it under Performance, not Security. The policy is just as strict either way.