Supabase is changing the shape of its API keys. The anon and service_role keys that most apps built in 2024 and 2025 still carry are being replaced by keys that start with sb_publishable_ and sb_secret_. The names are clearer, the rules are the same, and the change is a good moment to search your app for the one key that must never leave the server.
What the docs say
Supabase's documentation, as we read it on 2026-09-21, describes two key types. Publishable keys, prefixed sb_publishable_ and formerly called anon, are safe to expose in web pages, apps and source code. Anyone can read them, so they reach only what Row Level Security allows, acting as the anon or authenticated role. Secret keys, prefixed sb_secret_ and formerly called service_role, carry the BYPASSRLS attribute and skip every RLS policy. The docs say never to put one in a browser, a shipped app or source control. The legacy anon and service_role JWT keys are being deprecated by the end of 2026.
Supabase's 2025 security retrospective, published in January 2026, frames the change as part of a wider set (Supabase, 2026). That set includes RLS on by default for tables created in the dashboard, Security Advisors that flag tables without RLS, email alerts to project owners, and the new key formats replacing anon and service_role (Supabase, 2026).
A gloss for two terms. RLS, Row Level Security, is the set of per-row access rules Postgres applies to each query. A JWT is a signed token that carries claims about who is asking; the legacy keys were JWTs, which is why they were long strings starting with eyJ.
Why the rename helps
The old names invited a specific confusion. The word anon sounded like a low-power key, and service_role sounded like something a service would need, so both ended up in frontend code. Now the names say what each key is for. Publishable means publish it. Secret means do not.
Nothing about the permission model changes. A publishable key in the browser is still only as safe as the RLS policies behind it. A secret key in the browser still opens every table. What the rename gives a tester is a search term that cannot be mistaken for anything else.
What to check now: open your live app and search every loaded file for both the old and the new secret prefix. Steps: Service-role key shipped in the browser bundle.
What to search your bundle for
The bundle is the set of JavaScript files a browser downloads to run your app. Anything in it is public. Here is the search list, in order.
sb_secret_andservice_role. Either string in a loaded file means the key that skips every policy is in public hands. Rotate it in Project Settings, then find where the app put it.eyJ. Legacy keys are JWTs and start with these characters. A hit is expected for the anon key. Decode any hit in a browser-side JWT decoder and read theroleclaim;service_roleis the bad result.NEXT_PUBLIC_andVITE_variable names. On Next.js, the docs state that variables with theNEXT_PUBLIC_prefix are inlined into the browser bundle at build time. A secret key stored under that prefix is in the bundle whether or not the code appears to use it in the browser. Steps: NEXT_PUBLIC_ prefix on a secret.- The
apikeyrequest header. In the Network tab, click any request tosupabase.coand read theapikeyheader. Compare it with the keys in Project Settings. It should match the publishable or anon key, never the secret one.
If every search comes back clean, the remaining question is what the publishable key can reach. That is the RLS question, and the incident that illustrates it is Moltbook, where a publishable key in client JavaScript was by design and the exposure came from tables without RLS policies (Wiz Research, 2026). Steps: Row Level Security disabled on a table.
Planning the migration
The docs put the deprecation at the end of 2026. That leaves time, and the order matters.
- Create the new keys and swap the publishable key first; it is public anyway and the change is low-risk.
- Move the secret key into server-side configuration only: edge function secrets, your host's server environment variables, or your builder's backend secrets store. It should appear in exactly one place.
- Search the bundle again after deploying. A build cache can keep an old key alive for one more release.
- Rotate the legacy keys once nothing calls them, and watch the logs for a day.
Where testing comes in
Supabase did its job: it renamed the keys so that misuse reads as misuse, and it documented the rule. Confirming which role your deployed app actually carries is a separate job. We extract every credential-shaped string from the deployed bundle, decode each one and call the project's API with it to confirm which role it holds and which tables it reaches. That is part of our security package, and the Supabase hub lists every pattern we test for on this stack.
What to do this week
- Search every loaded file for
sb_secret_andservice_role: Service-role key shipped in the browser bundle. - List every
NEXT_PUBLIC_variable and ask whether the provider calls it secret: NEXT_PUBLIC_ prefix on a secret. - Confirm every table has RLS before you rely on the publishable key: Row Level Security disabled on a table.