Is it safe to expose the Supabase anon key?
Yes — the Supabase anon key is meant to be public and is safe to ship in your frontend, but only because Row Level Security is supposed to gate it. The moment one table has RLS off, the anon key reads that whole table. Safety comes from RLS, not from hiding the key.
This question comes up constantly because the anon key looks like a secret. It's a long eyJ... JWT, it sits in your .env, and every instinct says "don't commit that." But Supabase's own docs are blunt about it: the anon key is safe to expose in a browser if you have RLS enabled. It is designed to be embedded in client code. What actually protects your data is whether your policies are correct — not whether the key is visible.
So the honest answer is "yes, if." Let's make the if precise.
Is the Supabase anon key a secret?
No. The anon key is a public identifier that tells Supabase which project you're talking to and that the caller is unauthenticated (or carries a user's JWT). It grants exactly the access your RLS policies allow for the anon and authenticated roles — nothing more. That's why it's fine in your Next.js bundle, in a mobile app, or in a public repo.
Contrast it with the service_role key, which bypasses RLS entirely. That one is a secret, and shipping it to the browser is a full database compromise. The two keys look almost identical, which is exactly why they get confused — but their blast radius could not be more different.
| anon key | service_role key | |
|---|---|---|
| Meant to be public? | Yes | No — server only |
| Respects RLS? | Yes | No — bypasses every policy |
| Safe in the browser bundle? | Yes, with RLS on | Never |
| Blast radius if leaked | Limited by your policies | Your entire database |
When the anon key stops being safe
The anon key inherits every gap in your RLS. Here's the single failure that turns "public by design" into "public breach": a table with RLS turned off.
-- "Make the profiles table just work from the client"
create table public.profiles (
id uuid primary key references auth.users,
email text,
stripe_customer_id text
);
alter table public.profiles disable row level security;
With that one disable row level security, the profiles table — emails, Stripe customer IDs, everything — is readable and writable by anyone who opens devtools, copies your anon key, and hits the auto-generated REST endpoint. No login required. This is precisely the misconfiguration behind CVE-2025-48757, where a scan of AI-built (Lovable) apps found 303 endpoints across 170 projects leaking data through the public anon key. Here's what GuardLayer reports on exactly that migration — live engine output, not a mockup:
- Criticalsupabase/migrations/0001_profiles.sql:8
Row Level Security disabled
Re-enable RLS (ALTER TABLE <t> ENABLE ROW LEVEL SECURITY;) and add policies that scope access with auth.uid(). - Warningsupabase/migrations/0001_profiles.sql:2
Table created without enabling RLS
Add ALTER TABLE <table> ENABLE ROW LEVEL SECURITY; plus access policies right after the CREATE TABLE.
In May 2025, security researcher Matt Palmer scanned a batch of AI-generated Supabase apps and found 170 projects exposing data — 303 endpoints — to any caller holding the public anon key, all because RLS was never enabled. The key wasn't the problem. The missing policy was. (CVE-2025-48757)
The same is true of subtler mistakes: a table you forgot to enable RLS on, or a policy that's technically "on" but written as USING (true). In every case the anon key faithfully returns whatever your policies permit. If the policy is wide open, so is the key.
What about the new publishable and secret keys?
In mid-2025 Supabase introduced a new key format — a sb_publishable_... key that replaces the anon key, and sb_secret_... keys that replace service_role. Both key types work side by side today; Supabase has announced that the legacy anon and service_role JWTs will be phased out and removed in late 2026 (the exact date is still to be confirmed), with no action required before then. The security model doesn't change: the publishable key is still safe to expose and still gated entirely by RLS; the secret key is still server-only. The main win is rotation — a leaked secret key can now be revoked in seconds without invalidating every signed-in user's session, which was the painful part of rotating the old service_role key. If you're on a new project, you're already using these; on an older one, migrate before the deprecation deadline.
How to check you're actually safe
The anon key being in your bundle is fine. What you need to verify is that RLS backs it up:
-- Any table in the public schema with RLS OFF is exposed to the anon key.
select tablename
from pg_tables
where schemaname = 'public'
and rowsecurity = false;
If that query returns any row, the anon key can reach that table unauthenticated. Enable RLS and add a policy scoped with auth.uid():
alter table public.profiles enable row level security;
create policy "Users read own profile"
on public.profiles for select
using (auth.uid() = id);
Then confirm no service_role key is anywhere near your client — that's the key that genuinely must stay hidden. GuardLayer flags a disabled-RLS table, a missing policy, and an exposed service_role key on every push, with the fix inline.
FAQ
Is it safe to expose the Supabase anon key in frontend code? Yes. The anon key is public by design and is meant to be embedded in client-side code. Its access is constrained entirely by your RLS policies, so it's only as safe as your policies are correct. Enable RLS on every public table before relying on it.
Can I commit the anon key to a public repo?
Yes, it's not a secret. But never commit the service_role key — that one bypasses RLS and grants full database access. Confirm which key you're committing by checking the variable name and the JWT's role claim.
If the anon key is public, what actually protects my data?
Row Level Security. Every request made with the anon key runs as the anon or authenticated role, and RLS policies decide which rows those roles can see. No RLS means no protection.
Should I migrate to the new publishable key? Eventually, yes — Supabase has announced the legacy anon key will be removed in late 2026 (exact date to be confirmed). The publishable key behaves the same way (public, RLS-gated), so migration is low-risk, and the new secret keys are far easier to rotate if leaked.
Is the anon key the same as the JWT secret? No. The JWT secret signs user tokens and must stay private on the server. The anon key is a public API key. Leaking the JWT secret lets an attacker forge any user's session; leaking the anon key does nothing on its own if RLS is enabled.
Catch this before it ships — free
GuardLayer scans every push for this and 23 other Next.js + Supabase issues, with the exact fix inline.
No signup, no card — your code is scanned in memory and never stored.