Supabase's new API keys: sb_publishable_ vs sb_secret_
Every Supabase tutorial you find was written for two JWT keys: anon and service_role, both starting with eyJ. New projects now hand you two different strings instead:
sb_publishable_hfxxxxxxxxxxxxxxxxxxxxxx -- client, safe in the browser
sb_secret_oWxxxxxxxxxxxxxxxxxxxxxxxxxx -- server only, never ship it
Copying the old env var names and pasting the new values mostly works — until it doesn't, and you get one of these:
UNAUTHORIZED_INVALID_API_KEY
Invalid API key
Forbidden use of secret API key in browser
Which key goes where
| Key | Where | What it can do |
|---|---|---|
sb_publishable_ | Browser / Vue app | Anonymous requests, subject to RLS |
sb_secret_ | Serverless function only | Bypasses RLS — full data access |
Client setup
import { createClient } from '@supabase/supabase-js'
export const supabase = createClient(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY,
)
Server setup (Vercel function)
import { createClient } from '@supabase/supabase-js'
const admin = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SECRET_KEY!,
{ auth: { persistSession: false } },
)
The trap: anything prefixed
VITE_ is compiled into the JavaScript bundle and is public. Putting sb_secret_... behind a VITE_ name publishes your admin key. Supabase's own API rejects it from a browser context with "Forbidden use of secret API key in browser" — treat that message as a leak warning, not an error to work around.Why old tutorials fail on new projects
- Wrong header format. Server-side code copied from 2023 often sends only
Authorization: Bearer <anon>. With the new keys, send the key in bothapikeyandAuthorizationheaders when calling REST directly. - JWT assumptions. Code that decodes the key to read a
roleclaim will not work — these keys are opaque strings, not JWTs. - Key rotation. Legacy JWT keys still work on existing projects, so a tutorial written last year may be accurate for the author's project and wrong for yours.
Quick sanity check
Before debugging your app, test the key directly:
curl "https://<project>.supabase.co/rest/v1/profiles?select=id&limit=1" \
-H "apikey: <sb_publishable_...>" \
-H "Authorization: Bearer <sb_publishable_...>"
200 with an array means the key and grants are fine. 401 points at the key; 42501 points at missing grants — see the RLS guide.
Free: the Supabase + Stripe launch checklist — all 23 checks in one page, no email required to read the guides.
Get the free checklistSkip the migration archaeology
The starter is already on the new key system: publishable key in the Vue client, secret key only inside the Vercel functions.
Get the starter — $29 Try the live demo