# auth.role() and auth.email() are deprecated in policies

> Older policies use auth.role() = 'authenticated' and auth.email(). How to find them in your database and rewrite them with a TO clause and auth.jwt().

Source: https://supahost.app/kb/deprecated-auth-role · Updated 2026-11-11

## What you see

Policies like these, copied from older tutorials or brought over from another project:

```sql
using (auth.role() = 'authenticated')
using (auth.email() = invitee_email)
```

They still run, but both functions are deprecated and kept only for older projects.

## Why it happens

`auth.role()` reads the `role` claim from the request's JWT. Postgres already switches to that role for each request, and a policy's `to` clause can target it directly. That's clearer, and Postgres skips the policy entirely for other roles instead of evaluating a function on every row.

`auth.email()` reads the `email` claim. `auth.jwt() ->> 'email'` does the same and is the supported way to read any claim.

## How to fix it

1. **Find the affected policies** in Studio's SQL editor:

   ```sql
   select schemaname, tablename, policyname, roles, qual, with_check
   from pg_policies
   where coalesce(qual, '') || coalesce(with_check, '') ~ 'auth\.(role|email)\(\)';
   ```

2. **Replace `auth.role() = 'authenticated'` with a `to` clause.** `alter policy` can change the roles and the expression in place:

   ```sql
   -- Before: for select using (auth.role() = 'authenticated')
   alter policy "Members can read channels" on public.channels
     to authenticated
     using (true);
   ```

   Where the old check was combined with another condition, keep the condition:

   ```sql
   -- Before: using (auth.role() = 'authenticated' and auth.uid() = owner_id)
   alter policy "Owners can update channels" on public.channels
     to authenticated
     using ((select auth.uid()) = owner_id);
   ```

3. **Replace `auth.email()` with `auth.jwt()`:**

   ```sql
   alter policy "Invitees can see their invite" on public.invites
     to authenticated
     using (invitee_email = (select auth.jwt() ->> 'email'));
   ```

   Wrapping the call in `(select ...)` lets Postgres evaluate it once per query instead of once per row. Where you can, match on user ids rather than email addresses, because users can change their email.

4. **Drop checks for `service_role`.** A policy with `auth.role() = 'service_role'` does nothing useful: the `service_role` key bypasses Row Level Security anyway.

5. **Watch for `anon`.** `auth.role() = 'anon'` becomes `to anon`. If a policy should apply to visitors and signed-in users alike, use `to anon, authenticated`.

6. **Test as each role.** Use your app signed out and signed in, and check that reads and writes behave as before.

## On SupaHost

The MCP `get_advisors` tool checks for tables without RLS, tables with RLS but no policies, and functions with a mutable `search_path`. It doesn't flag deprecated functions in policies, so use the query in step 1.

## Related

- [Row Level Security](/docs/database/row-level-security)
- [Row Level Security, simplified](/kb/rls-simplified)
- [RLS performance best practices](/kb/rls-performance)
- [Roles, custom claims and RBAC](/docs/auth/custom-claims-rbac)
