Supabase foundations
Separate public and secret keys
Understand publishable or anonymous client keys, server-only secret keys, JWT context, and why RLS remains mandatory.
Every Supabase project ships with two kinds of API keys. Mixing them up is the fastest way to turn a small bug into a data breach, so let’s be clear about what each one does.
The publishable key starts with sb_publishable_. Older projects call it the anon key. This one is safe to ship in a browser or a mobile app. It identifies your project, nothing more. Requests made with it go through Row Level Security, and the signed-in user’s session adds identity on top.
The secret key starts with sb_secret_. Older projects have it as the service_role JWT. This one is a different animal. A client created with it bypasses Row Level Security entirely:
import { createClient } from '@supabase/supabase-js'
// server-side code only
const admin = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_SECRET_KEY
)
const { data } = await admin.from('notes').select()
console.log(data.length)
// every note from every user, no policy applied
That power is why the secret key must never reach a browser, a mobile bundle, a log line, or a public repository. A leaked secret key is an authorization incident. Every RLS policy you wrote stops protecting anything until you rotate it.
Verify what your build ships
Don’t trust your mental model. Check the artifact. After building your frontend, search the output for the two key formats:
grep -R "sb_secret" dist/
# (no output, this must find nothing)
grep -R "service_role" dist/
# (no output)
If either command prints a match, a privileged key made it into client code. Rotate it right away in the dashboard, under Settings → API Keys, then fix the import that leaked it. Rotation is not optional after exposure. The key does not expire on its own, and you cannot know who already copied it.
My advice is to run this grep in CI, on every build. It costs nothing, and it catches a wrong environment variable name before a user does.
The inventory habit
Take a small app and list every environment variable it uses. For each one, write down three facts: can it enter client code, where does the server-only copy live, and how do you rotate it.
The project URL and the publishable key can be public. The database password, the secret key, and SMTP credentials stay on the server, in your deployment platform’s secret store. If you can’t answer the rotation question for a variable, that’s the first thing to fix.
One warning to keep in mind. The publishable key is only safe to expose while RLS is enabled and correct on every exposed table. A public key plus an unprotected table is a public table. The key does not protect your data. The policies do.
Lesson completed