If you are seeing query performance degrade after enabling Row Level Security (RLS) on multi-tenant Supabase tables, the culprit is almost always per-row subquery evaluation.
The Problem: Per-Row Evaluation
When you write policies using subqueries:
CREATE POLICY "Tenant isolation" ON orders
FOR ALL USING (
tenant_id IN (SELECT tenant_id FROM user_tenants WHERE user_id = auth.uid())
);
PostgreSQL re-evaluates that subquery across every single scanned row rather than resolving it once upfront.
The Fix: Custom JWT Claims with InitPlan Wrapping
Inject the tenant_id into the user's JWT metadata on login and read it through a wrapped (SELECT ...) subquery:
CREATE POLICY "Fast tenant isolation" ON orders
FOR ALL USING (
tenant_id = (SELECT (auth.jwt() -> 'app_metadata' ->> 'tenant_id')::uuid)
);
Important: Wrapping
auth.jwt()inside(SELECT ...)is essential. Without the subquery wrapper, PostgreSQL evaluates the JSON claim extraction repeatedly for every row.
For the complete benchmark data and EXPLAIN (ANALYZE, BUFFERS) execution plans, read the full deep dive on my blog:
👉 Read Full Benchmark & Guide on blog.rowistan.com













