Supabase service_role key in your frontend bundle: replace it and switch the old key off

Updated Oct 27, 2026 · Pewterkit Guides

A Supabase service_role key in your website's JavaScript gives anyone full access to your project's data, because it skips Row Level Security. Create a new secret key under Settings > API Keys, use it only in server code, deactivate the legacy keys in the same section, and use the publishable (or legacy anon) key in the browser.

Why it matters

Supabase's API keys guide says service_role "has the BYPASSRLS attribute, so it skips every Row Level Security policy you attach," and that "A leaked secret key exposes all of your project's data." It lists "A web page, a public document, source code, or a bundled mobile, desktop, or CLI package" among the places a secret key must never appear.

The key that belongs in the browser is the publishable key, which Supabase calls "Safe to expose online" because it "only reaches what Row Level Security allows." The legacy anon key is the older version of it.

This mix-up is common in AI-built apps: when a query fails because of a Row Level Security policy, swapping in the service_role key makes the error go away, and also removes every protection.

Fix it

  1. Find where the key is used. Search your code for service_role, SERVICE_ROLE and createClient(. Every client created in browser code must use the publishable or anon key.
  2. Create a new secret key in the Supabase Dashboard under Settings > API Keys.
  3. Move privileged work to the server. Anything that truly needs to bypass Row Level Security (admin tasks, webhooks, scheduled jobs) goes into an Edge Function or another server route that reads the new secret key from a secret. Supabase: "If the compromised key is a JWT-based service_role key, replace it with the new secret key."
  4. Write Row Level Security policies for what users should be able to do from the browser, instead of bypassing them.
  5. Switch the old key off. Supabase: "For a legacy anon or service_role key, deactivate the legacy keys in the same Dashboard section instead." It adds that "Creating publishable and secret keys doesn't revoke your legacy keys", so this is a separate step. Deactivation is reversible if you find a client you missed (move that client to the new publishable key first).
  6. Redeploy and search the bundle again.

Supabase also advises: "Don't rush. Fix the root cause of the leak before you rotate anything." Otherwise the new key leaks the same way.

What Pewterkit Check looks at

The check reads your home page and the scripts it loads, decodes any Supabase-style JWT it finds, and reports one whose role is service_role, masked, with the file it was in. It also looks for other well-known secret formats (Stripe live secret keys, OpenAI sk-proj- keys and more). It never uses a key it finds. For the newer secret keys, Supabase says "A secret key doesn't work in a browser": it answers browser requests made with one with 401 Unauthorized.

FAQ

Is the anon key safe in my frontend?

It is meant for the browser, and what it can reach depends on your Row Level Security policies. Turn RLS on for every table that holds user data, and move to the new publishable key when you can.

Do I have to change my anon key too?

Supabase's docs describe deactivating "the legacy keys" as one step for anon and service_role, so move your browser code to the new publishable key first, then deactivate.

Can I keep using service_role on the server?

Yes, in server code only, but Supabase now treats it as the legacy version of secret keys. Use a new secret key for new server code.

Sources

  1. Understanding API keys (Supabase Docs) — read 27 Oct 2026

How this page was made: drafted with AI from the sources listed, reviewed by Pewterkit. Docs checked Oct 27, 2026. Spotted something out of date? Tell us.

Related guides