Skip to content
ShipCheck
Guides

Is the Supabase anon key safe to expose? Yes, if (and only if) RLS is right

Updated 18 October 2026

Short answer: yes, the Supabase anon key (and its newer replacement, the publishable key, sb_publishable_…) is designed to be shipped in your frontend. Supabase's own docs say it's safe to use in a browser as long as Row Level Security is enabled on your tables and your policies are correct. The key isn't the security boundary. RLS is.

What the anon key actually is

The legacy anon key is a JWT whose role claim is anon. When a request arrives with it, Supabase's API runs your query as the Postgres anon role (or as authenticated once a user signs in and their session token is sent). So the key identifies your project, not a privileged user. Anyone can copy it from your site, and that's expected.

What someone can do with your anon key

  • Anything the anon role is allowed to do by your RLS policies and grants: read, insert, update or delete rows in tables in exposed schemas (usually public).
  • Call any RPC function the anon role can execute.
  • Sign up users if email sign-up is enabled, and use other Auth endpoints.
  • Read files in public storage buckets (and private ones if storage policies allow).

If a table has RLS disabled, the first bullet means everything in that table. That's how most vibe-coded app data leaks happen.

The key you must never expose: service_role / secret

The service_role key (a JWT with "role": "service_role") and the newer secret keys (sb_secret_…) bypass RLS completely. If one appears in your frontend, anyone can read, modify or delete all of your data. Rotate it immediately in Supabase's API settings and move any code that needs it into an Edge Function or server.

How to tell them apart: paste a JWT into a decoder (or base64-decode the middle part) and look at the role claim. anon is fine to expose; service_role is not. ShipCheck does this automatically for every JWT it finds in your HTML and JavaScript bundles.

When the anon key is safe

  • RLS is enabled on every table in every exposed schema.
  • Policies are scoped (e.g. auth.uid() = user_id), and anything readable by anon is genuinely meant to be public.
  • Views use security_invoker (or aren't exposed), and security definer functions are reviewed.
  • Storage buckets holding private files are private, with policies.
  • Abuse protections are on where it matters: CAPTCHA on sign-up, rate limits, and email confirmation.

How to check yours in two minutes

  1. Run Supabase's Security Advisor (Advisors → Security Advisor) and fix the errors.
  2. Test what an anonymous visitor can read, table by table. See the Supabase RLS checker guide for the exact curl command.
  3. Search your live JavaScript for service_role and sb_secret_.

Should I hide the anon key anyway?

You can't really: a browser app has to send it to Supabase, so it will always be visible in network requests. Obscuring it adds no protection. If you need to keep a table completely private, don't rely on hiding the key. Lock the table with RLS and access it only from server-side code using the secret key.

Check your live app in 20 seconds. ShipCheck's free scan looks for leaked keys, a public Supabase setup, exposed files, missing headers and SEO blind spots. Run a free scan →