Symptoms
- Login, signup or any data call fails right after someone "cleaned up" keys in the Supabase dashboard.
- The response body or logs say
Legacy API keys are disabled. (The exact JSON varies; one example is quoted in supabase/cli#3893.) - The app worked before a free project was paused and restored, and broke afterwards (see cause 3).
- An Edge Function that calls Supabase with
SUPABASE_SERVICE_ROLE_KEYorSUPABASE_ANON_KEYstarts failing while the frontend works.
Why it happens
Supabase now has two key systems. The old ones are the anon and service_role keys, which are long-lived JWTs. The new ones are the publishable key (sb_publishable_..., safe in the browser) and secret keys (sb_secret_..., backend only). Supabase's docs say it "is deprecating the anon and service_role keys by the end of 2026" (Supabase: API keys).
Both systems work side by side until someone deactivates the legacy keys, which is a separate step in the dashboard (Supabase: Migrating to new API keys). Anything still holding an eyJ... key fails from that moment. The three usual ways it happens:
- Someone finished the migration but missed a caller. Supabase's migration guide warns: "There's no automatic usage indicator, so this is a manual check." Typical misses: a second app, a cron job, a webhook, a Database Webhook or
pg_netcall, or an old mobile build. - An Edge Function still reads the legacy variables. Supabase injects
SUPABASE_PUBLISHABLE_KEYSandSUPABASE_SECRET_KEYSalongside the legacySUPABASE_ANON_KEYandSUPABASE_SERVICE_ROLE_KEY, and the legacy ones "carry the legacy keys" (Supabase: API keys). - A paused project was restored. Supabase's announcement timeline says "Projects restored from 1st November 2025 will no longer be restored with the legacy API keys" (supabase discussion #29260). The fix is the same.
AI builders make this more likely: many tutorials and generated code still tell you to paste a long eyJ... key. Supabase's docs say that if "a tool, tutorial, or AI assistant tells you to copy a long key that begins with eyJ, it was written for the legacy keys."
Step-by-step fix
1. Find out which caller is failing
Open the browser DevTools Network tab on your live site and retry the failing action. A failing request to https://<project>.supabase.co/... with this message means the problem is in your frontend code. If the frontend is fine, check your Edge Function logs and any server, cron or webhook that calls Supabase.
2. Get the new keys
In the Supabase dashboard, open Settings > API Keys and the Publishable and secret API keys tab. If you only see a Create new API keys button, the project has never had them. Creating them is safe and doesn't touch the legacy keys (migration guide, step 1).
3. Swap the browser key
Replace the anon key with the publishable key wherever the frontend creates its client:
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY // was the anon key (eyJ...)
)The publishable key carries the same low privileges as anon, so your RLS policies behave the same (migration guide, step 2).
If the key comes from an environment variable on Vercel, Netlify or another host, update it there and rebuild. VITE_ and NEXT_PUBLIC_ values are baked in at build time (Vite: Env variables, Next.js: Environment variables).
A community thread reports that an old supabase-js (2.39) failed with the new keys (answeroverflow). If the swap doesn't work, upgrade @supabase/supabase-js to the current release first.
4. Swap server keys and Edge Functions
Replace service_role with a secret key in every backend. In Edge Functions, read the new variable instead of the legacy one (migration guide, step 4):
// before
const secretKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
// after
const secretKey = JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!)['default']Send the new keys on the apikey header, not Authorization: Bearer. They aren't JWTs, so anything that tries to verify one as a JWT fails (Supabase: API keys).
5. Check the easy-to-miss callers
From Supabase's own list: app versions already in users' hands, CI/CD pipelines, third-party integrations and webhooks, cron jobs, workers, pg_net calls and Database Webhooks.
6. Stopgap only: re-activate legacy keys
Deactivation is reversible: "You can re-activate them if you find a client you missed" (migration guide, step 6). Don't re-activate if the keys were turned off because one leaked. That puts the leaked key back in service.
On Lovable Cloud (Lovable's built-in backend) you don't manage the Supabase project directly. Lovable's docs don't describe how it handles this key change (checked 19 Oct 2026), so ask Lovable in the project chat or contact Lovable support rather than editing keys by hand.
Fix prompt for your AI builder
Paste this into Lovable, Bolt, Cursor, Replit, v0 or whichever tool you build with:
My Supabase calls fail with "Legacy API keys are disabled". Fix only the Supabase key setup, nothing else:
1. Find every place the app or its Edge Functions read a Supabase key (env vars, createClient calls, Deno.env.get).
2. In browser code, use the publishable key (sb_publishable_...) from an env var such as VITE_SUPABASE_PUBLISHABLE_KEY. Never put a secret or service_role key in browser code.
3. In Edge Functions, replace Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') with JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!)['default'], and SUPABASE_ANON_KEY with the matching SUPABASE_PUBLISHABLE_KEYS entry.
4. Remove any hard-coded key that starts with eyJ.
5. List every file you changed and every env var I need to set on my host, then tell me to rebuild.
Don't change auth flows, UI or database policies.How to confirm it's fixed
- Rebuild and redeploy, then open the live site in a private window.
- Sign up or log in with a test account and load a page that reads data.
- In DevTools Network, check that Supabase requests return 2xx and that no request still carries an
eyJ...key in theapikeyheader. - Trigger each Edge Function, cron and webhook once and check their logs.
How to find out sooner next time
A key change like this breaks things after the button is pressed, and Heedza never submits forms or signs in. So it won't tell you that a login fails. What it can do: Heedza checks on a schedule that your landing, signup, login and checkout pages load and still show their forms. The free scan reads your homepage and a few of its scripts and flags exposed keys, including a Supabase service_role key that has slipped into frontend code during a migration like this one.
Let Heedza check this for you. Heedza’s security checks looks for secret keys left in your page code and private files anyone can open. It checks again every time it checks your pages. It never logs in or fills in forms, so it can’t see errors that only show after that.
Made with Lovable, Bolt, Replit, Cursor or v0? Free for 1 site: start free or see prices.