auth_rls_initplan: Detects RLS policies that re-evaluate auth functions for each row. CODE_BLOCK: auth_rls_initplan: Detects RLS policies that re-evaluate auth functions for each row. CODE_BLOCK: auth_rls_initplan: Detects RLS policies that re-evaluate auth functions for each row. CODE_BLOCK: -- BEFORE: auth.uid() called for every row scanned CREATE POLICY "assignments_update_teacher" ON public.assignments FOR UPDATE TO authenticated USING ( EXISTS ( SELECT 1 FROM public.profiles p WHERE p.id = auth.uid() AND p.role IN ('teacher', 'admin') ) ); CODE_BLOCK: -- BEFORE: auth.uid() called for every row scanned CREATE POLICY "assignments_update_teacher" ON public.assignments FOR UPDATE TO authenticated USING ( EXISTS ( SELECT 1 FROM public.profiles p WHERE p.id = auth.uid() AND p.role IN ('teacher', 'admin') ) ); CODE_BLOCK: -- BEFORE: auth.uid() called for every row scanned CREATE POLICY "assignments_update_teacher" ON public.assignments FOR UPDATE TO authenticated USING ( EXISTS ( SELECT 1 FROM public.profiles p WHERE p.id = auth.uid() AND p.role IN ('teacher', 'admin') ) ); CODE_BLOCK: -- AFTER: auth.uid() runs once, planner caches the result CREATE POLICY "assignments_update_teacher" ON public.assignments FOR UPDATE TO authenticated USING ( EXISTS ( SELECT 1 FROM public.profiles p WHERE p.id = (SELECT auth.uid()) AND p.role IN ('teacher', 'admin') ) ); CODE_BLOCK: -- AFTER: auth.uid() runs once, planner caches the result CREATE POLICY "assignments_update_teacher" ON public.assignments FOR UPDATE TO authenticated USING ( EXISTS ( SELECT 1 FROM public.profiles p WHERE p.id = (SELECT auth.uid()) AND p.role IN ('teacher', 'admin') ) ); CODE_BLOCK: -- AFTER: auth.uid() runs once, planner caches the result CREATE POLICY "assignments_update_teacher" ON public.assignments FOR UPDATE TO authenticated USING ( EXISTS ( SELECT 1 FROM public.profiles p WHERE p.id = (SELECT auth.uid()) AND p.role IN ('teacher', 'admin') ) ); COMMAND_BLOCK: # Supabase CLI (v2.81.3+) supabase db advisors # Or via the dashboard: # https://supabase.com/dashboard/project/<ref>/advisors/performance COMMAND_BLOCK: # Supabase CLI (v2.81.3+) supabase db advisors # Or via the dashboard: # https://supabase.com/dashboard/project/<ref>/advisors/performance COMMAND_BLOCK: # Supabase CLI (v2.81.3+) supabase db advisors # Or via the dashboard: # https://supabase.com/dashboard/project/<ref>/advisors/performance COMMAND_BLOCK: -- For each flagged policy: DROP + recreate with the wrapped call DROP POLICY "<name>" ON public.<table>; CREATE POLICY "<name>" ON public.<table> FOR <command> TO <roles> USING (<original expression with (SELECT auth.uid())>); COMMAND_BLOCK: -- For each flagged policy: DROP + recreate with the wrapped call DROP POLICY "<name>" ON public.<table>; CREATE POLICY "<name>" ON public.<table> FOR <command> TO <roles> USING (<original expression with (SELECT auth.uid())>); COMMAND_BLOCK: -- For each flagged policy: DROP + recreate with the wrapped call DROP POLICY "<name>" ON public.<table>; CREATE POLICY "<name>" ON public.<table> FOR <command> TO <roles> USING (<original expression with (SELECT auth.uid())>); CODE_BLOCK: -- BAD: planner can't inline a plpgsql function, every call is opaque CREATE FUNCTION public.is_admin() RETURNS boolean LANGUAGE plpgsql STABLE AS $$ BEGIN RETURN EXISTS ( SELECT 1 FROM public.profiles p WHERE p.id = (SELECT auth.uid()) AND p.role = 'admin' ); END $$; -- GOOD: SQL functions get inlined into the policy at plan time CREATE FUNCTION public.is_admin() RETURNS boolean LANGUAGE sql STABLE AS $$ SELECT EXISTS ( SELECT 1 FROM public.profiles p WHERE p.id = (SELECT auth.uid()) AND p.role = 'admin' ); $$; CODE_BLOCK: -- BAD: planner can't inline a plpgsql function, every call is opaque CREATE FUNCTION public.is_admin() RETURNS boolean LANGUAGE plpgsql STABLE AS $$ BEGIN RETURN EXISTS ( SELECT 1 FROM public.profiles p WHERE p.id = (SELECT auth.uid()) AND p.role = 'admin' ); END $$; -- GOOD: SQL functions get inlined into the policy at plan time CREATE FUNCTION public.is_admin() RETURNS boolean LANGUAGE sql STABLE AS $$ SELECT EXISTS ( SELECT 1 FROM public.profiles p WHERE p.id = (SELECT auth.uid()) AND p.role = 'admin' ); $$; CODE_BLOCK: -- BAD: planner can't inline a plpgsql function, every call is opaque CREATE FUNCTION public.is_admin() RETURNS boolean LANGUAGE plpgsql STABLE AS $$ BEGIN RETURN EXISTS ( SELECT 1 FROM public.profiles p WHERE p.id = (SELECT auth.uid()) AND p.role = 'admin' ); END $$; -- GOOD: SQL functions get inlined into the policy at plan time CREATE FUNCTION public.is_admin() RETURNS boolean LANGUAGE sql STABLE AS $$ SELECT EXISTS ( SELECT 1 FROM public.profiles p WHERE p.id = (SELECT auth.uid()) AND p.role = 'admin' ); $$;
- Per-user write tables like chapter_progress or quiz_attempts. One row per (student × content), grows linearly with engagement.
- Many-to-many junctions like enrollments. Scanned during nearly every authenticated read.
- Open your project's Advisors → Performance tab. Look for auth_rls_initplan. If the list is non-empty, you have the trap.
- Write a single migration that DROPs and recreates the flagged policies with (SELECT auth.uid()) everywhere auth.uid() appears bare. Don't try to refactor the policies' logic at the same time. Pure mechanical change.
- While you're in the advisor, glance at multiple_permissive_policies and policy_exists_rls_disabled. Both compound the same per-row cost. Multiple permissive policies on the same role + action each run separately, so two bad policies double the trap.
- If you have helper functions in policies, verify each is LANGUAGE sql STABLE with a single SELECT body. Convert any plpgsql ones if you can keep the logic in pure SQL.
- Has this lint caught anything for you besides auth.uid()? auth.jwt() ->> 'role' should hit the same code path but I haven't traced it directly.
- If you've written is_member_of(...) helpers for multi-tenant RLS, did you keep them in SQL or move to plpgsql? Curious about the tradeoff at scale.
- For anyone who left these unwrapped on purpose — what does that constraint look like? I can imagine a few cases where you'd want the per-row eval, but I can't think of any in policy code specifically.
- Free forever — MIT-licensed, no paywalls, no "premium" tiers.
- Simple to deploy — one-click Vercel deploy with a free Supabase database. No Docker, no servers to manage.
- Built for small scale — optimized for 20-100 students, not…