Supabase key checker

Paste a Supabase key, an .env file or a code snippet. In one second you see which key it is (anon, service_role, publishable, secret, access token or a user's session), which project it belongs to, when it expires, and whether it may sit in a browser.

Runs in your browser. Nothing you paste leaves this page.

One key, or a whole file: every Supabase key in it is checked.

Or try:

Checks that the key belongs to this project.

Paste a key to see what it is.

The verdict shows up as you paste. The checker knows these formats:

  • Publishable keysb_publishable_…Fine in a browser
  • Legacy anon keyeyJ… with role anonFine in a browser
  • Secret keysb_secret_…Server only
  • Legacy service_role keyeyJ… with role service_roleServer only
  • Personal access tokensbp_…Never in an app
  • User session tokeneyJ… with a user in itNot an API key

What the Supabase key checker tells you

Supabase gives every project two kinds of keys: a public one that your frontend ships to every visitor, and a server one that skips every security rule. They are easy to mix up. The legacy keys are both long JWTs starting with eyJ, and the only thing that tells them apart is one word hidden in the middle: the role. This checker decodes that part in your browser and names the key, so you know where it may live before you paste it anywhere else.

KeyLooks likeRoleIn a browserIf it leaked
Publishable keysb_publishable_...anonYesNothing to rotate: check RLS
Legacy anon keyeyJ... with "role": "anon"anonYesNothing to rotate: check RLS
Secret keysb_secret_...service_roleNo, server onlyReplace it, then delete it
Legacy service_role keyeyJ... with "role": "service_role"service_roleNo, server onlyMove to new keys, switch legacy off
Personal access tokensbp_...You, on the Management APINever in an appRevoke it
User session tokeneyJ... with sub and emailauthenticated, one userNot an API keyIt expires on its own
Formats from Supabase's API keys documentation. Supabase says it is deprecating the legacy anon and service_role keys by the end of 2026.

Nothing you paste leaves the page: there is no request to Supabase or to Keybrum, and the input is never logged. That also means the checker reads the key without proving it works, which matters for the limits below.

Each check, and why it matters

  • Role. A key with the anon role gets only what your row level security (RLS) policies allow. A key with the service_role role has Postgres's BYPASSRLS attribute: it reads, changes and deletes every row, every stored file and every user account, whatever your policies say.
  • Project. Legacy keys carry the project reference (ref). Add your project URL and the checker compares the two: a key from another project is answered with a 401, which is a common cause of an app that suddenly shows nothing. Publishable and secret keys do not carry the ref, so they cannot be matched this way.
  • Issued and expiry. The iat and exp claims are shown as dates. An expired key is refused by Supabase. A user's session token lives for a short time and is refreshed by the client.
  • Where it sits. When you paste a file, the checker reads the variable name. Values in NEXT_PUBLIC_, VITE_, EXPO_PUBLIC_ or REACT_APP_ variables are copied into the JavaScript bundle at build time, so a server key there is already public. It also catches the classic slip: a variable named ANON_KEY that holds the service role key.
  • Demo keys. A payload with iss: supabase-demo is the example key of self-hosted Supabase. Its JWT secret is published on GitHub, so an instance that still trusts it lets anyone sign a service_role key.

How to read the result

The first line is the verdict, colored by what it means for you. Green: a public key, fine in a browser. Red: a key that must stay on a server, with the steps to retire it if it ever left. Amber: something to look at, such as a session token, an expired key or a key from the wrong project. Each row below the verdict says what was found, why it matters and the fix, with a copy button.

A green verdict is about the key, not about your data. The anon and publishable keys are public by design: what they can read is decided table by table by your policies. That is where real leaks happen. CVE-2025-48757 was assigned after 170 of 1,645 Lovable apps tested let strangers read or write data through missing or weak RLS, with a public key that was working exactly as intended. Our guide to the Supabase anon key shows what that key reaches, and the Supabase RLS checker lists the tables it can read.

How to fix a service role key that went public

If a service_role or secret key was ever in a frontend, a git repository, a screenshot or a chat, treat it as leaked. Deleting it from the code does not take it back: old builds and git history keep it. Fix the cause first, then retire the key on Supabase's side. For a legacy service_role key, Supabase's current flow is:

  1. 1

    Move the key out of the frontend

    Read it only in server code: an API route, a server action or an Edge Function. Remove any public prefix from the variable name.

  2. 2

    Create the new keys

    In Settings > API Keys, create a secret key for your server and a publishable key for your apps. Deploy both.

  3. 3

    Switch the legacy keys off

    In Settings > API Keys, deactivate the legacy keys. This retires the anon and the service_role key together, so do it after every client uses the new keys.

  4. 4

    Revoke the legacy JWT secret

    In Settings > JWT Keys, migrate the legacy secret to signing keys, rotate, then revoke it. Supabase notes that anything still trusting that secret can accept the leaked key.

  5. 5

    Look for damage

    Check your tables, storage buckets and Authentication > Users for changes you did not make.

A leaked sb_secret_ key is simpler: create a new secret key, swap it everywhere, then delete the old one in Settings > API Keys. Deleting cannot be undone, and the publishable key and user sessions are not affected. The server side should look like this:

ts
// app/api/admin/route.ts: server only, never imported by a component
import { createClient } from "@supabase/supabase-js";

const supabaseAdmin = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SECRET_KEY!, // no NEXT_PUBLIC_ or VITE_ prefix
  { auth: { persistSession: false, autoRefreshToken: false } },
);
Check who is calling before you use this client: it skips every policy. The full playbook is in our guide to the Supabase service role key.

Moving from the anon key to a publishable key is the same idea on the browser side, and changes nothing about what the key can reach. See anon key vs publishable key for the migration.

What a static check cannot tell you

  • Whether the key still works. The checker never calls Supabase, so it cannot tell an active key from a deactivated one. It also does not verify the signature: an edited token decodes the same way.
  • Where the key has been. It reads the key you paste. It cannot know whether that key is in your live bundle, an old deploy or a public repository.
  • What your policies allow. A public key is only as safe as your RLS. The checker does not see your tables.
  • Which project owns a new key. Publishable, secret and access tokens carry no project ref.

For the keys that are not from Supabase (OpenAI, Stripe, AWS), use the API key leak checker. If a key already leaked, what to do with a leaked API key gives the order to fix things in.

Questions

Is the Supabase anon key safe to expose?

Yes, if row level security is on, with sound policies, for every table your API exposes. The anon key and the publishable key are meant to ship in your frontend. They give the anon role, and your policies decide what that role reads. See the anon key guide.

How do I tell the Supabase anon key from the service role key?

Both legacy keys are JWTs starting with eyJ. Decode the middle part: it says "role": "anon" or "role": "service_role". The checker above does it in your browser. With the new keys it is visible at a glance: sb_publishable_ is public, sb_secret_ is server only.

What is the difference between sb_publishable and sb_secret keys?

The publishable key replaces the anon key and is safe in a browser. The secret key replaces the service_role key, skips RLS and belongs on a server. Supabase also refuses a secret key sent from a browser, but that check reads the User-Agent header, which a script can set to anything.

What should I do if my Supabase service role key was exposed?

Move it to server-only code, create new secret and publishable keys, deactivate the legacy keys, then revoke the legacy JWT secret in Settings > JWT Keys. Then check your data for changes. The service role key guide walks through each step.

Is it safe to paste my Supabase key into this checker?

The checker runs entirely in your browser: no request is made, nothing is stored or logged. Still, a service role key you pasted anywhere is a key you handled outside your server. If it came from a frontend or a repo, rotate it either way.

Where do I find my Supabase keys?

In the Supabase dashboard, under Settings > API Keys, or in the Connect dialog of your project, which shows the URL and the publishable key for your framework. Personal access tokens are under Account > Access tokens.

More free tools

Your app is public. So are its mistakes.

Check what any attacker can already see in it, free, in ten seconds. The full attack, with proof and fixes: €49 once, or €29 a month on a plan.

or continue with Google or GitHub