Guides · Updated October 15, 2026
Is my Supabase anon key safe in my frontend?
You built your app with Lovable, Bolt, Replit or v0, and anyone can see a long Supabase key in your frontend code. That anon key, and the newer publishable key, are meant to be public. Your data stays private through the rules inside your database, and this guide shows you how to check and fix them.
What the anon key and the publishable key are
Supabase gives every project two kinds of keys. The browser key is the anon key on older projects and a publishable key starting with sb_publishable_ on newer ones. Supabase describes the anon key as the legacy version of the publishable key, and lists the publishable key as “safe to expose online” in a web page, a mobile app or source code (API keys docs).
The server key is the service_role key on older projects and a secret key starting with sb_secret_ on newer ones. It bypasses Row Level Security and gives full access to your project’s data. Supabase is deprecating the anon and service_role keys by the end of 2026 in favor of publishable and secret keys.
Why the anon key is public by design
Your app runs in each visitor’s browser, so the browser has to receive the key. A request made with the publishable key and no signed-in user runs as the anon Postgres role, and the key itself does not decide what a visitor can see. In Supabase’s words: “Anyone can read it, so it only reaches what Row Level Security allows.”
Row Level Security (RLS) is a set of rules inside your database that decides which rows each person can read, add, change or delete. Grants are a second layer that decide whether a role can use a table at all (RLS docs). Set up well, those two layers keep your data private while the key stays public.
When the anon key becomes a problem
The public key becomes a risk in four common situations.
RLS is off on a table. Supabase warns that “a table in an exposed schema without RLS is readable and writable by any role with a grant on it.” On existing projects, a new table in public starts with select, insert, update and delete granted to anon (until the October 30 change below), so anyone with your anon key can read and change it while RLS is off.
A policy lets everyone in. A policy written as to anon using (true) gives every signed-out visitor read access to every row the role can reach. Supabase says to use it only for data that is meant to be public.
Grants to anon are broader than the app needs. Adding policies doesn’t take grants back. A table that only signed-in users should change, with insert, update or delete still granted to anon and a loose policy, is open to the public.
The secret key ends up in the frontend. The service_role or sb_secret_ key bypasses RLS, and Supabase says exposing a secret key “puts all of your project’s data at risk.” It should never appear in browser code or in a build variable that ships to the browser.
How to check your app
Start in the Supabase dashboard with the Security Advisor, which flags problems such as RLS disabled on public tables and permissive RLS policies (Database Advisors docs).
Next, open each table in public, confirm that RLS is enabled, and read every policy. Look for using (true) on private data and for update or delete open to signed-out visitors. Our guide to checking RLS on every table has SQL that lists them all at once.
Finally, search your live app’s JavaScript and your build settings for service_role, sb_secret_ and any unfamiliar long key. Check your git history too, since an old commit can still hold a removed key.
How to fix it
Turn on RLS for every table. In the SQL Editor, run alter table public.your_table enable row level security; for each table. Once RLS is on, the publishable key can’t reach any data in that table until you add policies.
Scope policies to auth.uid(). It returns the ID of the user making the request. A select policy from the Supabase docs:
create policy "Users can view their own profile."
on profiles for select
to authenticated
using ( (select auth.uid()) = user_id );Write one policy for each action, and use with check on insert and update so people can only write their own rows.
Keep the secret key on the server. Use it only in server code, such as an Edge Function, stored as a secret.
Rotate a leaked secret key before you clean your code. The old key keeps working until you retire it. Supabase’s steps: create a new secret key under Settings → API Keys, replace the old key everywhere your app uses it, confirm every part of the app uses the new one, then delete the old secret key. A legacy service_role key is deactivated in the same section instead. Supabase also says to fix the cause of the leak first, so make sure the new key lives only on your server. Then remove the old key from your code and git history. More in what to do when you find a key in your public JavaScript.
The October 30, 2026 Supabase change
New tables in public stop being exposed to the Data API automatically. This became the default for new projects in a rollout starting May 30, 2026, and reaches existing projects on October 30, 2026 (Supabase changelog). After that, a new table needs an explicit grant, and a query without one fails with error 42501, “permission denied for table”.
The error’s hint suggests a grant for whichever role made the request, which is anon when nobody is signed in. Don’t grant everything to anon, and don’t turn RLS off to make the error go away. Grant only what your app needs, usually to authenticated, and keep RLS on with policies scoped to auth.uid():
grant select, insert, update, delete on public.your_table to authenticated;
alter table public.your_table enable row level security;Give anon select only on tables that are meant to be public. More in our October 30 guide.
Get a second look
Prodseal checks apps built with Lovable, Bolt, Replit, v0 and similar tools on Supabase. The free check looks at what any browser receives from your public URL, such as keys in your JavaScript. Connect GitHub (Prodseal opens pull requests and never merges them) or Supabase (read-only) for a free full report on your RLS rules, logins, storage buckets, keys in git history and more. For $49 one time, Prodseal writes and tests fixes for the Critical and High issues it can fix automatically, delivered as a pull request or as SQL plus a Lovable prompt, with manual steps for the rest, such as rotating a leaked key.
Questions go through prodseal.dev/contact.
Run my free check