Supabase · public keys

Supabase API keys: publishable vs secret, anon vs service_role

Publishable/anon keys are safe in browser code; secret/service_role keys bypass RLS and belong on the server. Compare formats, roles, and migration.

Published · Updated · Reviewed by SafeForProd

Supabase has two low-privilege client keys (publishable, and its legacy form anon) and two privileged server keys (secret, and its legacy form service_role). Client keys are designed to be public and are constrained by Row Level Security. Server keys bypass RLS entirely and must never ship to a browser. A visible publishable or anon key is usually expected — it is not a leak, and it tells you almost nothing about whether RLS actually protects your data.

Do not rotate a publishable key merely because a scanner found it in JavaScript. First classify the key using the Supabase dashboard and current provider documentation.

The four keys at a glance

Publishable keySecret keyLegacy anon keyLegacy service_role key
StatusCurrentCurrentLegacy — Supabase calls it the “legacy version of publishable keys”Legacy — the “legacy version of secret keys”
FormatPrefixed token: sb_publishable_...Prefixed token: sb_secret_...Long-lived JWT (eyJ...)Long-lived JWT (eyJ...)
Intended environmentPublic clients: web, mobile, desktop, CLIsBackend only: servers, secured admin APIs, Edge Functions, microservicesPublic clientsBackend only
Privilege levelLowElevatedLowElevated
Postgres role usedanon when signed out, authenticated when signed inservice_role with the BYPASSRLS attributeanon / authenticatedservice_role with RLS bypass
Row Level SecurityAppliesBypassed — full access to project dataAppliesBypassed — full access to project data
Safe in browser code?Yes — Supabase documents it as “safe to expose online”Never — “never use in a browser, even on localhost”; the API returns HTTP 401 when it detects browser useYes, but legacyNever

Primary references:

Direct answers to the common questions

Is the anon key the same as the publishable key?

Functionally, yes. Supabase describes anon as the legacy version of the publishable key, and the migration guide states that the publishable key “carries the same low privileges as the anon key, so your Row Level Security policies behave the same.” Supabase Auth is unchanged either way: the user still signs in and gets their own JWT.

The differences are mechanical, not permission-related:

  • Legacy anon keys are long-lived JWTs; publishable keys are prefixed, non-JWT tokens.
  • New keys do not touch your project’s JWT secret.
  • New keys are sent on the apikey header; sent via Authorization: Bearer, the platform tries to parse them as a JWT and rejects the request.

Is service_role the same as the secret key?

Functionally, yes — and this is the dangerous equivalence. Both authorize requests as the built-in service_role Postgres role, which Supabase documents as having full access to your data and using the BYPASSRLS attribute, “skipping any and all Row Level Security policies you attach.”

Secret keys add protections the legacy service_role key does not have:

  • Browser-use blocking: a secret key used from a browser (matched on the User-Agent header) always receives HTTP 401.
  • Multiple named keys: you can issue one secret key per backend service, so a single leak forces one rotation instead of every server credential at once.
  • Non-JWT format: rotation no longer means rotating the project’s JWT secret.

Which Supabase key is safe in browser code?

The publishable key — or the legacy anon key in older projects. Both are low-privilege by design, and Supabase explicitly calls the publishable key safe to expose online.

Never in browser code: the secret key or the legacy service_role key. Either one gives any visitor full, RLS-bypassing access to the project’s data. If owner-side verification confirms a current secret or service_role key is in a public bundle, treat it as compromised: revoke and rotate it, move the replacement server-side, and rebuild the deployed assets.

What does a visible public key prove — or not prove — about RLS?

A visible publishable or anon key proves only that the client is using the intended low-privilege key class. That avoids a false alarm. It does not validate the database policy:

  • It does not prove RLS is enabled on your tables.
  • It does not prove policies exist for SELECT, INSERT, UPDATE, and DELETE.
  • It does not prove USING and WITH CHECK conditions enforce the tenant and ownership rules you intended.
  • It does not prove the authenticated role cannot reach rows it should not.

Those rules can be correct, missing, or overly broad regardless of which client key is public. Key class can often be settled from public context and owner verification; RLS correctness requires source, migrations, or dashboard access; and actual role behavior requires explicit authorization, verified domain control, approved test accounts, and bounded requests.

A safe client configuration

This is a normal browser-side pattern when the values are the project URL and a publishable or legacy anon key:

import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  import.meta.env.VITE_SUPABASE_URL,
  import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY,
)

Vite will place VITE_ variables in the client bundle. That is acceptable only for values designed to be public. See Vite: Env Variables and Modes.

The security boundary is not “nobody can see the publishable key.” It is the combination of the signed-in user’s JWT, database grants, and RLS policies.

An unsafe client configuration

This hypothetical example puts a privileged server key into public code:

// Unsafe: a privileged server credential is bundled for every visitor.
const admin = createClient(
  import.meta.env.VITE_SUPABASE_URL,
  import.meta.env.VITE_SUPABASE_SERVICE_ROLE_KEY,
)

If owner-side verification confirms that the value is a current service-role or secret key, revoke and rotate it, remove the replacement from all client-prefixed variables, move the privileged operation to a backend or Edge Function, and rebuild the deployed assets.

Do not use a found key against the project to “see if it works.” Verification should happen through the owner’s dashboard and approved source review.

Why the prefix is not enough

Legacy Supabase anon and service-role keys are both JWT-shaped strings. A generic rule that reports every eyJ... token as a database secret will produce false positives. A provider-aware check should consider:

  • where the value appears;
  • the variable name and surrounding client initialization;
  • the provider’s current key format (sb_publishable_ and sb_secret_ prefixes, or the legacy JWT pair);
  • the declared role when it can be inspected without using the credential;
  • whether owner-side verification is still required.

A public bundle containing a Supabase URL and publishable key is Informational. A privileged-key pattern in public code may be Needs verification until the owner confirms the role. It becomes Confirmed only when deterministic evidence within scope establishes that a privileged credential is public.

Legacy key deprecation: current status

Supabase’s current API-key and migration documentation says the legacy anon and service_role keys are deprecated by the end of 2026. The rollout began with the milestones Supabase published in its original announcement:

WhenWhat changes
June 2025Early preview of the new keys on all projects. New projects automatically generate both new and legacy keys.
July 2025Full launch. Dashboard and docs focus on the new keys.
November 2025Monthly migration reminders begin. Projects restored from 1 November 2025 are not restored with legacy keys. New projects no longer have anon and service_role available.
By the end of 2026Current Supabase documentation says the legacy keys are deprecated and projects should use publishable and secret keys instead.

Two practical caveats:

  • Both key types work simultaneously during migration. Creating current keys does not revoke the legacy pair, and deactivating the legacy pair is reversible. Check the current dashboard before relying on a remembered cutoff or migration state.
  • Migration has compatibility traps: publishable and secret keys belong on the apikey header; Edge Functions’ built-in verify_jwt does not validate the new key format, so the function must use the current Supabase authorization approach; and mobile apps, CI pipelines, workers, pg_net, Database Webhooks, and third-party integrations can keep an old key alive. Supabase’s migration guide is the source of truth for the current sequence.

For security classification, the migration changes nothing: the privileged half of the pair was never browser-safe, in either format.

What the free scan can tell you

The free scan can identify Supabase configuration in referenced public assets, classify an obvious publishable or legacy anon key as Informational, flag a privileged-looking pattern as Needs verification, and attach the exact redacted location. It does not query a table, call Supabase, decode private user data, or use the key.

See the sample public-surface scan report for an example that separates public evidence from RLS unknowns.

What requires an Authorized source scan or Human security review

The planned Authorized source scan can review client and server initialization, environment-variable use, migrations, grants, policies, and approved role behavior after the account, source, authorization, and domain gates are satisfied.

A Human security review is appropriate when policies encode complex tenant, organization, invitation, admin, or billing rules that need business context — cross-tenant reads, role changes, and recovery flows are exactly the decisions a visible key can never validate. The sample human security review shows how that evidence is reported.

The vibe coding security checklist places this review in the broader launch sequence.

Limitations

Supabase is evolving from legacy JWT keys toward publishable and secret key formats, and Supabase may adjust the timetable above. Use the current dashboard label and provider documentation as the source of truth. Key classification does not replace a policy review, and a clean free scan does not mean RLS passed.

Revision history

  • 2026-07-27: Updated the legacy-key deadline and migration caveats to match Supabase’s current API-key, migration, and signing-key documentation.
  • 2026-07-24: Added the four-key comparison table, direct answers (anon vs publishable, service_role vs secret, browser-safe keys, what a visible key proves about RLS), the published legacy-key migration timetable with behavioral differences, and links to the differentiated sample reports.
  • 2026-07-20: Initial publication covering publishable/anon versus secret/service-role keys and public-surface limitations.