buildwiththis home

Supabase publishable key vs anon key: what to switch, and what we tested

Short answer: Supabase’s publishable key (sb_publishable_…) replaces the legacy anon key and is just as public: it’s meant to sit in your front end, and Row Level Security decides what it can reach. In our test it behaved exactly like the anon key. The secret key (sb_secret_…) replaces service_role, bypasses Row Level Security, and must never reach a browser. Supabase is deprecating the anon and service_role keys; both systems work side by side until you turn the old ones off.

Tested and sources checked Oct 16, 2026Checklist, not a tool verdict

Quick answers

Is the Supabase publishable key the same as the anon key?

Same job, new format. Supabase’s docs list anon as the “Legacy version of publishable keys” and say the publishable key “carries the same low privileges as the anon key, so your Row Level Security policies behave the same.” In our test, both keys got exactly the same results on the same tables. Publishable keys are short strings, not JWTs.

Which Supabase key can go in my front end?

Only the publishable key (or the legacy anon key). Supabase: “Anything you ship: browser, mobile app, CLI, script” uses the publishable key. The secret or service_role key must never be in browser code, a public repo, or an environment variable your bundler exposes, such as one starting with NEXT_PUBLIC_ or VITE_.

Do I have to switch right now?

Not today. Supabase says creating the new keys “doesn’t revoke your legacy keys. Both key systems work at the same time,” and that it is deprecating the anon and service_role keys. Switch before Supabase turns them off; we don’t give a date here because Supabase hasn’t published an exact one.

Is it safe that my publishable key is visible?

Yes, by design, but it’s only as safe as your Row Level Security. In our test the publishable key could read exactly the rows a policy allowed and nothing else. In our security test, a table with RLS off let anyone holding the public key read and add rows, and a loose policy let any signed-in user read and edit every profile.

Does the publishable key show my database schema?

No. In our test, asking the API for its schema (GET /rest/v1/) with the publishable key returned 401 “Secret API key required”. The legacy anon key was refused too, with “Only the service_role API key can be used for this endpoint.”

Why do I get “Invalid JWT” after switching?

The new keys aren’t JWTs. Supabase’s migration guide says to send them on the apikey header only; if the key is also on Authorization: Bearer, “the platform tries to parse it as a JWT and rejects the request with Invalid JWT,” and Edge Functions need verify_jwt = false plus their own check. On the database REST API in our test, the publishable key on both headers still worked. We didn’t test Edge Functions.

Does my project still have the legacy anon key?

Check Settings → API Keys. Our test project, created on 2026-10-15, still had an enabled legacy anon key next to its publishable key, according to Supabase’s management API. Older projects may have only legacy keys; Supabase says you’ll see a “Create new API keys” button.

What we tested

On 2026-10-16 at about 4:25 PM ET, on our own Supabase test project (free plan, created 2026-10-15), we made a small table with Row Level Security on: two public rows, two private rows, a policy that lets anyone read the public rows, and no insert, update or delete policies. We sent the same requests to the database REST API with the legacy anon key and with the publishable key, then deleted the table.

RequestLegacy anon keyPublishable key
Read a table where a policy allows public rows200, the 2 public rows200, the same 2 rows
Read the private rows200, no rows200, no rows
Insert a row (no insert policy)401 “new row violates row-level security policy”Same
Update or delete a row (no policy)200, nothing changed200, nothing changed
Read a table with RLS on and no policy200, no rows200, no rows
Read the API schema (GET /rest/v1/)401 “Invalid API key” · “Only the service_role API key can be used for this endpoint.”401 “Secret API key required” · “Only secret API keys can be used for this endpoint.”
Key on both apikey and Authorization: Bearer200200
Sent with a browser Origin and User-Agentnot run200 (publishable keys are meant for browsers)

Result: swapping anon for publishable changed nothing about what the key could reach. Row Level Security gave identical answers. The only difference was the error text on the schema endpoint.

Not tested: a real secret key sent from a browser (Supabase says it returns 401, matched on the User-Agent header; we had no secret key to test with and didn’t create one), the error you get after deactivating the legacy keys, Edge Functions, signed-in users, and whether GitHub secret scanning flags an sb_secret_ key (secret scanning on private repos is a paid add-on, and we won’t push even a dummy secret to a public repo for this). We also didn’t look at the dashboard itself.

The four Supabase keys

KeyFormatAccessWhere it goes
Publishablesb_publishable_…, short stringLow: Row Level Security appliesBrowser, mobile app, CLI, scripts
Secretsb_secret_…, short stringFull: bypasses Row Level SecurityYour server, Edge Functions, cron jobs only
anon (legacy)Long JWT starting eyJLow, like publishableReplace with publishable
service_role (legacy)Long JWT starting eyJFull, like secretReplace with secret

Supabase’s rule of thumb: write your policies for the anon and authenticated roles. “Policies never apply to a secret key, because service_role has the BYPASSRLS attribute.” And a handy tell from its docs: “If a tool, tutorial, or AI assistant tells you to copy a long key that begins with eyJ, it was written for the legacy keys.”

The switch checklist

Based on Supabase’s migration guide, in plain language.

  1. Create the new keys. Settings → API Keys → Publishable and secret API keys. If you see a “Create new API keys” button, your project only has legacy keys so far. Your old keys keep working.
  2. Put the publishable key in your front end. Replace the anon key wherever your client is created. Only the publishable key may use your framework’s public prefix (NEXT_PUBLIC_, VITE_).
  3. Put the secret key on your server only. Never behind a public prefix, never in a repo, never pasted into an AI chat. Supabase lets you make a separate secret key per backend, so one leak means one rotation.
  4. Send the new keys on the apikey header. Supabase’s guide warns that a new key on Authorization: Bearer can be rejected with “Invalid JWT” (Edge Functions also need verify_jwt = false and their own check).
  5. Find every place that still has an old key: shipped mobile apps, CI/CD, webhooks, cron jobs, other services. Supabase: “There’s no automatic usage indicator, so this is a manual check.”
  6. Deactivate the legacy keys, then test your app. Supabase says this is reversible if you find a client you missed.
  7. Check your Row Level Security either way. Switching keys doesn’t fix a missing or loose policy. Run our security checklist. Security Advisor says “RLS Disabled in Public”? See the safe fix we tested.

If an AI coding tool built your app

Ask it to replace only the anon key with the publishable key in the client code, and to keep the secret key in server-side environment variables. Don’t paste a secret key into the chat. Then search your code for eyJ and sb_secret_ to make sure no secret ended up in the front end. In our security test, a service-role key planted in front-end code was caught by Aikido, Semgrep and Prodable’s free scan, but GitHub secret scanning didn’t flag it.

If a table your AI tool just created returns 42501 “permission denied for table”, the key isn’t the problem: see our tested fix for Supabase error 42501.

Error messages we saw

What we sentWhat came back
No apikey header (key only on Authorization: Bearer, or no key)401 “No API key found in request” · “No `apikey` request header or url param was found.”
Made-up or wrong-project key401 “Invalid API key” · “Double check the provided API key for typos. This API key might also be owned by another Supabase project.”
Publishable key asking for the schema401 “Secret API key required”
Insert blocked by Row Level Security401, code 42501, “new row violates row-level security policy for table …”

What switching does and doesn’t fix

Does: secret keys you can rotate one at a time, one key per service, and (per Supabase) secret keys that refuse to work in a browser.

Doesn’t: fix your policies. In our security test, every scanner missed a policy that let any signed-in user read and edit every profile. The public key is only as safe as your Row Level Security.

Sources

Related: add login to your app (users still sign in with their own session; the key switch doesn’t change login) · database for your vibe-coded app · Vibe coding hub

General guidance from one small test project, not a security audit. Never paste a real key into a page, chat or repo; the examples here use placeholders.