Supabase anon key vs service_role key: which one can be public?
· 5 min read
If you built your app with Lovable, Bolt or Cursor on top of Supabase, you have probably seen a long key starting with eyJ sitting in your front-end code. Is that a leak? Usually not. But there is a second key that looks almost the same and must never be there.
Two keys, two very different powers
Every Supabase project comes with (at least) two API keys:
| Key | Where it belongs | What it can do |
|---|---|---|
anon / publishable key | Front end, public | Whatever your Row Level Security policies allow an anonymous or signed-in user to do |
service_role / secret key | Server only | Everything. It bypasses Row Level Security |
Newer Supabase projects use keys that start with sb_publishable_ and sb_secret_ instead of JWTs, but the rule is the same: publishable is public, secret is not.
The anon key is public on purpose
The anon key identifies your project; it is not a password. Supabase is designed so the browser can talk to the database directly, which means the key has to be in the code every visitor downloads.
What protects your data is Row Level Security (RLS). With RLS on, every query made with the anon key is filtered by your policies, for example "users can only read rows where user_id equals their own id".
The service_role key must never reach the browser
The service_role key skips RLS entirely. If it ends up in your JavaScript bundle, anyone can copy it and read, change or delete every row in every table, no matter how good your policies are.
AI tools sometimes put it there to "fix" a permission error: the query fails because of RLS, so the code switches to the key that ignores RLS. The error goes away and the database is now open.
If you find a service_role or sb_secret_ key in public code:
- Rotate it now in the Supabase dashboard (Project Settings → API keys).
- Move the code that needed it into an Edge Function or your server, reading the key from a secret.
- Fix the RLS policy that caused the original error instead of bypassing it.
How to check your app
- In the Supabase dashboard: open Table Editor and check that every table shows RLS as enabled. The Security Advisor also lists tables without RLS.
- In your code: search for
service_role,sb_secret_andSUPABASE_SERVICEin anything that runs in the browser. - In your live site: a FixPrompt scan checks your public JavaScript for
service_roleJWTs andsb_secret_keys, and flags apps that talk to Supabase directly so you know to review RLS. It never queries your data.
Prompt for your coding agent
Check this project's Supabase setup:
1. List every table and whether Row Level Security is enabled. Enable it where it is off and
add policies that limit rows to the owner (auth.uid() = user_id), unless the table is public.
2. Search all front-end code for service_role keys, sb_secret_ keys or SUPABASE_SERVICE env vars.
Move that logic into Edge Functions and tell me which keys to rotate.
Show me the SQL and how to test that another user cannot read my rows.
For a full list of Supabase checks, see the Supabase security checklist.
Check your app in minutes
Paste your URL and get a plain-English report with prioritized risks and a fix prompt for your AI. Your first scan is free.
Scan my app free