pgrls + Supabase — if I use Supabase, do I need pgrls?

Short answer: yes, especially. Supabase ships the RLS engine (auth.uid(), auth.role(), auth.jwt(), the anon / authenticated / service_role triad). It does not ship a linter for the policies you write against that engine. pgrls is the missing piece.

The kind of bug pgrls catches that Supabase doesn’t

Supabase docs warn about some of these and the rest fly under the radar. The two that bite hardest in real-world Supabase codebases:

(1) IS NULL OR … admits every row to anonymous clients.

CREATE POLICY tenant_read ON public.documents
    FOR SELECT
    USING (auth.uid() IS NULL OR owner_id = auth.uid());

auth.uid() returns NULL for unauthenticated requests; the IS NULL branch is true; the OR short-circuits; anonymous clients see every row. The Supabase RLS docs explicitly recommend writing this the other way (IS NOT NULL AND … = auth.uid()) — but nothing checks that you did. pgrls flags it as SEC004.

(2) user_metadata is end-user writable.

USING (auth.jwt() -> 'user_metadata' ->> 'role' = 'admin')

Any authenticated user calls supabase.auth.updateUser({ data: { role: 'admin' }}) from the client SDK; the next JWT carries it; the policy reads it; the user is admin. The safe counterpart is app_metadata (service-role-only). pgrls flags this as SEC033.

Capability check

  Supabase pgrls
Hosted Postgres + auth + storage
Provides the RLS engine
Ships a CI linter for RLS policies
Catches the IS NULL OR … footgun docs warn flags & fails CI
Catches the user_metadata bypass
Per-row perf trap (PERF001 wrap) docs recommend flags every miss
Bug surfaces in CI vs in production production CI

Wire pgrls into a Supabase project

supabase start runs a local Postgres; pgrls runs against it:

- run: supabase start
- run: pgrls lint --database-url "$(supabase status -o env | grep DB_URL | cut -d= -f2)" --schemas public

Or the one-liner in GitHub Actions (uses your hosted DB URL from a secret):

- uses: pgrls/pgrls-action@v1
  with:
    database-url: $
    schemas: public

Verdict

Supabase ships the engine; pgrls ships the linter. Using both is the default sane setup. Most Supabase RLS bugs that ship to production fall into one of the rule classes pgrls already catches — see the Supabase recipe for the canonical CI wire-up.