Supabase anon key vs service role key: is yours safe to expose?
Supabase gives you two keys. One is meant to be public and one must never leave your server, and on the older keys the only difference between them is a word buried inside the key. Paste one and find out which you have.
Checked in your browser — never sent, stored or logged
The result appears as soon as you paste — there is no button to press. Your keys are in the Supabase dashboard under Settings → API Keys.
Two keys, one of which ends careers
Supabase is retiring the anon and service_role keys by the end of 2026 in favour of publishable and secret keys. The rule doesn't change: the public one goes in the frontend, the secret one never does.
anon key
legacy JWT starting eyJ · replaced by the publishable key, sb_publishable_…
Belongs in your frontend
Runs as the anon role, or authenticated once someone signs in, so it can only reach what your row-level security policies allow. On a table with security switched off, that is everything on it.
service_role key
legacy JWT starting eyJ · replaced by the secret key, sb_secret_…
Server-side code only, never a browser
Ignores every policy you have written. Reads, changes and deletes anything in your database, including other people's rows.
Anon, service_role and what to do
Checking a whole app rather than one key? Scan your published site or see which variables reach the browser.
What is the difference between the anon key and the service_role key?
The anon key is designed to be public and ships in your frontend. What it can reach is decided by your row-level security policies. The service_role key ignores those policies entirely — it is an administrator key, meant for server-side code, and anyone holding it can read and change all of your data.
Is the Supabase anon key safe to expose?
Yes, as long as row-level security is on. The anon key, and the publishable key that replaces it, are made to sit in a browser or a mobile app where anyone can read them. They run as Postgres's anon role, or as authenticated once a user signs in, so they reach exactly what your policies allow. On a table with RLS switched off, that is every row.
Is it a problem that my anon key is visible in my JavaScript?
No. That is what it is for, and every Supabase app works this way. The thing worth checking is not whether the key is visible but whether row-level security is enabled on your tables — with it off, that public key reads your data for anyone who asks.
Where do I find my anon and service_role keys?
In your project's Dashboard under Settings > API Keys, which lists every key the project has, legacy or new. There is no separate Settings > API page any more. The Connect dialog also shows your project URL and publishable key, ready to paste into a .env file.
What are the publishable and secret keys?
Their replacements. Supabase is retiring the anon and service_role keys by the end of 2026. The publishable key starts with sb_publishable_ and does the anon key's job; the secret key starts with sb_secret_ and does service_role's. Creating the new keys doesn't revoke the old ones, which keep working until you disable them. One improvement worth having: a secret key refuses to work from a browser, so the mistake this page checks for fails loudly instead of quietly.
What does RLS have to do with my keys?
Everything, for the public key. Row-level security policies are rules on each table saying which rows a role may read or change, and they are the only thing standing between your anon or publishable key and your data. Write them for the anon and authenticated roles. They never apply to service_role or a secret key, which bypass RLS by design.
I found a service_role key in my app. What now?
Rotate it in your Supabase dashboard immediately, before anything else. Assume the current value is known — a key that has been in a browser bundle is public whether or not anyone has used it yet. Then move whatever needed it into server-side code, such as an edge function.
Do you store the key I paste?
We cannot. This page has no server behind it: the check runs in your browser, in JavaScript you can read, and there is no request to send the key anywhere. You can disconnect from the internet and it will still work.
How did a service_role key end up in my app in the first place?
Usually by pasting it into an environment variable with a public prefix — NEXT_PUBLIC_ or VITE_ — because the app would not work without a key and that was the key to hand. It is a very easy mistake, and one audit of AI-built apps found roughly a third of them had made it.