Supabase anon key vs service role key: which is safe to expose?
The anon key is designed to be public: it ships in your frontend, and every query made with it is filtered through your row-level security policies. The service role key is the opposite: it bypasses those policies entirely, so it belongs only on your server, and finding it in frontend code is a critical incident.
Applies to Supabase, legacy and new API keysSeverity depends which keyDetected by static scan, no code execution
| anon key | service role key | |
|---|---|---|
| Current name | Publishable key (sb_publishable_…) | Secret key (sb_secret_…) |
| Designed to be | Public. Ships in your frontend bundle | Private. Server-side only |
| Row-level security | Every query is filtered by your RLS policies | Bypasses RLS entirely |
| If it leaks | Usually fine. Confirm RLS is on every table | Rotate immediately. Treat it as a breach |
| Where it belongs | Browser code, NEXT_PUBLIC_ / VITE_ variables | Server env vars, API routes, edge functions |
Two keys, one rule
Every Supabase project has two API keys, and everything about their safety comes down to how they interact with row-level security. The anon key carries no special power. It identifies your project, and then RLS policies decide what each request may read or write. That's why Supabase puts it in your frontend quickstart: visibility was part of the design.
The service role key exists for the opposite job. Backend tasks like admin dashboards, cron jobs, and migrations need to touch any row, so the service role key skips RLS entirely. That's safe on a server you control and catastrophic anywhere else. The danger was never that a key is visible. The danger is which key is visible.
One caveat keeps the anon key honest: it's only as safe as your policies. If a single table has RLS disabled, the anon key can read that whole table. A leaked anon key doesn't cause that exposure, it just makes the missing policy reachable. The fix is the policy, not hiding the key.
The rename: publishable and secret keys
In 2025 Supabase started replacing the legacy JWT-based keys with new ones that say what they are. The publishable key (sb_publishable_…) replaces the anon key. The secret key (sb_secret_…) replaces the service role key. New projects only get the new keys, and the legacy anon and service_role keys are deprecated at the end of 2026.
The safety rule survives the rename untouched: publishable is for the browser, secret never leaves your server. The new names also fix the worst part of the old system. Secret keys can be created per backend service and revoked individually, so one leak no longer forces you to rotate everything at once.
How to tell which key you're looking at
New keys are self-describing: sb_publishable_ is public, sb_secret_ is not. Legacy keys both start with eyJ and look identical, because both are JWTs. Paste one into a JWT decoder and read the role claim:
The same distinction decides where an environment variable may live. A public prefix like NEXT_PUBLIC_ or VITE_ ships the value to every browser, which is correct for one key and a breach for the other:
What to do if you mixed them up
The service role key is in your frontend
This is the critical case. Rotate the key first, then move the new one to a server-only environment variable, then redeploy. The full sequence, including why that order matters, is in our guide to an exposed service role key.
A scanner flagged your anon key
Secret scanners like GitGuardian flag every Supabase key they see in a public repo, including the one that's supposed to be public. That alert alone is not a breach. Spend the energy on the question that matters instead: is RLS enabled, with real policies, on every table holding user data? If yes, you're done. If no, the anon key was never your problem.
How to verify you're safe
service_role and sb_secret_. Zero matches.NEXT_PUBLIC_ and VITE_ variables whose values are secret or service role keys. The public prefix is a promise to ship the value to every visitor.USING (true) is as open as having it off. Read each policy and check it scopes users to their own rows.If a security tool told you your anon key is exposed, don't panic, and don't pay to have it "fixed". Half the internet flags this key as a leak; it's designed to be seen. The question worth an audit is whether the key that must stay hidden is hidden, and whether the policies gating the public one actually exist.
Fair questions
Is it safe to expose the Supabase anon key?
Yes, if row-level security is enabled on your tables. The anon key ships in every Supabase frontend by design, and every query made with it is filtered through your RLS policies. The key being visible is not the risk. A table with RLS disabled is the risk, because the anon key can then read all of it.
What can someone do with my anon key?
Exactly what an anonymous visitor to your site can do, no more. The key identifies your project, then your RLS policies decide what each request may touch. If your policies are correct, a stranger with your anon key sees only what a stranger should. If a table has RLS off, they can read that whole table, but the fix is the policy, not hiding the key.
GitGuardian or a scanner flagged my Supabase key on GitHub. Do I need to rotate it?
It depends which key leaked. The anon or publishable key: rotation is usually unnecessary, spend the time confirming RLS instead. The service role or secret key: rotate immediately and treat it as a breach, because that key bypasses every access rule you have.
What are sb_publishable_ and sb_secret_ keys?
The current names for the same two roles. The publishable key replaces the anon key, the secret key replaces the service role key. New projects only get the new keys, and the legacy ones are deprecated at the end of 2026. The safety rule is unchanged: publishable is for the browser, secret never leaves your server.
My key starts with eyJ. Which one is it?
The prefix alone can't tell you; both legacy keys are JWTs. Paste it into a JWT decoder and read the role claim: anon is the public one, service_role is the admin one.
Ascertify is a static code audit for AI-built and agency-delivered apps. It reads your code without running it and reports what's exposed, what's missing, and what to fix first, in plain English, pinned to the exact file and line. Telling these two keys apart, and checking the policies behind the public one, is among the first things it looks at; the free preview tells you how many issues your code has.
Read-only access. Your code is sandboxed, never executed, and deleted after the scan.