Supabase Performance Advisor
What does auth_rls_initplan mean, and how do I fix it?
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.

It is listed with the Security Advisor warnings. For those, see what each Supabase Security Advisor warning means.
Checklist
How to fix auth_rls_initplan, then check the fix
- 01Read 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'; - 02Rewrite 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' ); - 03Run 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 - 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
Related guides
Sources
Official docs behind this checklist
Browse all launch guides · Read resources · How to make and check your next change
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.