sb_publishable_ keys and the new API-key hierarchy
The classic Supabase keys were JSON Web Tokens wearing costumes: opaque strings whose meaning hid in a decodable payload claiming a role. The newer model replaces costume with label — publishable keys and secret keys whose names state their nature, prefixed sb_publishable_ and sb_secret_. The trust model does not change. The ergonomics improve considerably.
This page explains the hierarchy and the migration mechanics as a hand-maintained reference. RowShield does not automate key-migration advice; what it does do, noted near the end, is recognise every generation of key when scanning deployed code.
RowShield does not detect this yet. This guide gives you the catalog queries to check it yourself. The nine rules that do ship are listed on the rules index.
From claims-in-a-JWT to names-that-say-it
Legacy keys packed their privilege level into a signed token: decode the payload and a role claim — anon or service_role — announced where the key sat on the trust spectrum. Functional, but it meant classification required decoding, humans compared keys by eyeballing prefixes that all began identically, and the security-critical information travelled in the most machine-readable format imaginable while looking like noise to everyone else.
The newer formats discard the disguise. A publishable key, sb_publishable_-prefixed, occupies the public slot the anon key held: safe in bundles, bounded entirely by Row Level Security. A secret key, sb_secret_-prefixed, occupies the privileged slot: server-side only, authority comparable to the old service_role. Separate management tokens, sbp_-prefixed, govern the account plane and never belong in application traffic. Names map to handling rules without decoding anything.
-- The hierarchy at a glance (values synthetic): sb_publishable_xk3… -- public: bundles, headers, docs sb_secret_q7f… -- privileged: server env, secrets manager sbp_t9m… -- management plane: never in app code eyJhbGciOi…REDACTED -- legacy generation; decode payload for its role
What genuinely improves
Three gains are immediate. Classification collapses from decode-and-inspect to glance-at-prefix, which matters enormously for scanners, reviewers and future-you at two in the morning. Replacement separates from revelation: credentials become values you swap rather than tokens whose structure constrains rotation, easing the rotation-after-leak playbook this knowledge base covers elsewhere. And the naming teaches the model — a developer who has never read the docs correctly infers that publishable goes in the bundle and secret does not, which the JWT format never managed in years of trying.
What improves least is security posture, and saying so plainly prevents a dangerous inference: a publishable key in a browser is exactly as exposed as the anon key ever was, and remains exactly as safe — which is to say, safe while and because RLS bounds it. The new model changes credential ergonomics, not database enforcement.
Migrating without breakage
The mechanics are environmental rather than architectural. Generate the new-format keys in the dashboard alongside the old ones, then move surfaces one at a time: replace the public key wherever the bundle consumes it, replace the privileged key in server environments, and adjust the conventional variable names — NEXT_PUBLIC_SUPABASE_ANON_KEY and its siblings — so the labels match reality. Redeploy, exercise the app, then sweep repositories and deployed bundles for lingering legacy strings before retiring the old keys. Application code rarely changes at all, because client libraries accept either generation.
Keep the mental model fixed throughout: publishable replaces anon, secret replaces service_role, and every rule this knowledge base states about one applies verbatim to its successor. Teams that re-learn the model per format re-commit the old mistakes under new prefixes.
Recognition across generations
Tooling that inspects deployed code must speak every dialect, because migrations leave strata — this year's publishable key beside last year's never-quite-retired JWT. RowShield's bundle scanner recognises the full set: legacy JWTs classified by decoding the role claim inside, sb_publishable_ and sb_secret_ keys by prefix, sbp_ management tokens as privileged-by-definition. A secret-format or service-role-classified key in browser-reachable code is a critical finding; publishable keys produce none, being exactly where they belong. Fingerprints are stored rather than values, requests stay GET, HEAD and OPTIONS, and RowShield is an independent product, not affiliated with or endorsed by Supabase.
RowShield does not advise on or automate the migration itself — the steps above are manual, as stated upfront. For the detection half, scan any deployed URL at rowshield.dev/audit; the check is free and needs no signup.
Frequently asked
- Are legacy JWT-shaped keys insecure compared to the new ones?
- No — the two generations express the same trust spectrum, and a legacy anon key bounded by good policies is no worse than a publishable one. The new formats improve clarity, classification and rotation ergonomics rather than the underlying security model.
- Is an sb_publishable_ key safe to commit to a public repository?
- It is safe in the sense the anon key always was: designed for exposure, bounded by Row Level Security. Most teams still keep it in environment configuration for consistency, but its presence in a bundle or repository is not an incident.
- What are sbp_ tokens and where do they belong?
- They are management-plane credentials for account and project administration, a different plane from the data API entirely. They bypass nothing less than everything, so they belong in a secrets manager and nowhere near application code or bundles.
Check your project in about ten seconds
Paste a URL. No signup, no writes, nothing stored.
Run the free audit