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().

What you see

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

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:

    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:

    -- 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:

    -- 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():

    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.

Updated 11 November 2026 · View as Markdown · Something wrong or missing? Tell us