Skip to content
All posts
Supabase

anon key vs service_role key, explained for founders

Which Supabase key is safe in the browser, which one bypasses Row Level Security, and how to check your app in two minutes.

By Mike ShobitanPublished Oct 23, 20265 min read

Every Supabase project comes with two kinds of API key. Mixing them up is the most common serious mistake I see in AI-built apps, and it's easy to make because both look like long random strings.

The short version

Public keySecret key
Namesanon (legacy) or sb_publishable_…service_role (legacy) or sb_secret_…
Safe in the browser?Yes, by designNever
Respects Row Level Security?YesNo, it bypasses RLS
Where it belongsYour frontendServers, Edge Functions, CI

Why the public key is fine to ship

The public key only identifies your project. What a request can actually do is decided by Row Level Security policies in your database. With RLS on and good policies, someone holding your public key can only do what a visitor or signed-in user is allowed to do.

That's also why RLS matters so much. With RLS off, the public key can read and write the whole table. Nothing in between is checking.

Why the secret key is an emergency

The secret key skips RLS entirely. It's meant for trusted server code that needs to do admin work, like a webhook that updates any user's subscription. If it ends up in your frontend bundle, anyone who opens dev tools has full read and write access to your database.

How it usually gets there in AI-built apps:

  • The builder hit an RLS error, and “fixed” it by switching to the key that ignores RLS.
  • An env var was named with a public prefix (VITE_, NEXT_PUBLIC_), which bundles it into client code.
  • Someone pasted it into a config file that the frontend imports.

How to check in two minutes

  1. Open your live app, then your browser's dev tools, then the Sources or Network tab.
  2. Search all loaded JavaScript for service_role and sb_secret_.
  3. For legacy JWT keys, paste any long eyJ… string into a JWT decoder and look at the role claim. anon is fine. service_role is not.

If you find it

  1. Rotate it in the Supabase dashboard right away. Removing it from the code isn't enough: it's already in old deploys and caches.
  2. Move whatever needed it into an Edge Function or server route that holds the new key as a secret.
  3. Fix the RLS policy that made the builder reach for it in the first place.
  4. Check your logs for unusual reads or writes while it was exposed.

This check is part of every Launch Risk Diagnostic. If you'd rather not do it alone, that's what it's for.

Not sure what's wrong?

Start with the diagnostic. The $250 fee is credited to the fix.