Supabase anon key vs service_role key: the difference
Both are long tokens starting eyJ. One is public by design; the other bypasses every security policy you have written.
Open your Supabase project's API settings and you get two long strings that begin the same way and differ somewhere in the middle. Copying the wrong one into the wrong file is the single most expensive typo available in this stack.
The difference in one line each
- anon (newer name: publishable) — obeys Row Level Security. Belongs in your frontend. Public by design; Supabase's documentation says so.
- service_role (newer name: secret) — ignores Row Level Security completely. Belongs on your server only. Treat it like a database password, because that is what it is.
Tell them apart in ten seconds
Decode the role claim of any key you find
# paste the key in place of THE_KEY echo "THE_KEY" | cut -d. -f2 | base64 -d 2>/dev/null | grep -o '"role":"[^"]*"' # expected output for a frontend key: # "role":"anon"
The middle segment of a JWT is plain base64 — no secret needed to read it. If that command prints service_role for a key you found in your deployed JavaScript, treat it as an emergency and rotate before you do anything else.
Check which one is in your live bundle
- 1Open your deployed app and press F12.
- 2Sources tab, then search all files with Ctrl+Shift+F for: service_role
- 3Search again for: SUPABASE_SERVICE — a mis-prefixed environment variable is the usual route in.
- 4Decode any long eyJ string you find with the command above and read its role claim.
- 5A hit means the key is in the bundle every visitor downloads, and has been for as long as that build has been live.
Why the wrong key ends up in the browser
- It was named with a client-exposed prefix — VITE_, NEXT_PUBLIC_ or REACT_APP_ — which compiles it into the bundle by design.
- An AI tool reached for it to make a query 'just work' when RLS was blocking the anon key.
- It was pasted into a server file that later became a client component.
- Both keys were copied into the same .env and the wrong variable was imported.
Audit which Supabase keys my project uses and where each one runs. 1. Find every Supabase key in the project — source, config, environment files and git history. Decode each long eyJ token and report its role claim, plus the newer sb_publishable_ / sb_secret_ keys by prefix. 2. For each, tell me whether the code holding it runs in the browser or on the server. 3. Any service_role or sb_secret_ key reachable from the browser is an emergency: tell me first, then move it to a server-only environment variable with no VITE_, NEXT_PUBLIC_ or REACT_APP_ prefix. 4. Rewrite the affected calls to use the anon key and work through RLS. If an operation genuinely requires elevated access, move that operation to a server route or an Edge Function instead of moving the key. 5. Tell me explicitly whether the key appears in git history, because that means rotating regardless of what I change in the code. Do NOT solve an RLS error by switching to the service_role key.
Common questions
- What is the difference between the anon and service_role keys?
- The anon key is bound by Row Level Security and is designed to sit in your frontend. The service_role key bypasses RLS entirely — every policy you wrote is skipped — so it can read, modify and delete every row in every table, for every user. Only the second one is a secret.
- How can I tell which key I am looking at?
- Both are JSON Web Tokens, so the role is readable in the middle segment. Decode that segment and look at the role claim: anon is the public one, service_role is the dangerous one. Newer projects use sb_publishable_ and sb_secret_ prefixes, which say it on the label.
- When do I legitimately need the service_role key?
- Server-side work that must ignore user permissions: scheduled jobs, admin tooling, webhook handlers, migrations. It belongs in a server environment variable with no client-exposed prefix, and never in code that reaches the browser.
- My AI tool swapped the anon key for the service_role key. Why?
- Because a query was failing under RLS and the service_role key makes it succeed. It is the fastest way to clear the error and the most damaging: it silences the symptom by removing the protection that produced it.
This is one check out of 40+
Paste your site address and we run the whole list from the outside — leaked keys, open databases, unprotected pages — then hand you one prompt that fixes what we find. Free, about 30 seconds, no signup.
Check my site — free