Supabase is replacing its legacy JWT-based anon and service_role keys with new keys, sb_publishable for the client and sb_secret for the server, and the migration causes JWT errors in Edge Functions because the new keys are not JWTs. When a client sends an sb_publishable key on the Authorization Bearer header, which many Supabase clients do by default, the platform tries to parse it as a JWT and rejects the request as an Invalid JWT. The fixes are to send the new key on the apikey header instead, and for Edge Functions to deploy with the no-verify-jwt option so the platform does not JWT-verify it. But no-verify-jwt turns off the platform's authentication gate, so it is only safe for genuinely public functions or when you add your own authentication inside the function.
Short answer
The Invalid JWT error after migrating is because the new keys are not JWTs but are sent where the platform expects one. Per Supabase's new API keys guide, sb_publishable replaces the anon key and sb_secret replaces the service_role key, and these should be sent on the apikey header, not Authorization Bearer, which the platform tries to parse as a JWT. Deploying an Edge Function with the no-verify-jwt option stops the platform from JWT-verifying it. Per Supabase's Edge Function security docs, no-verify-jwt removes the platform's auth check, so it is safe only for public functions or when you authenticate inside the function yourself.
What replaces the old anon key?
Supabase is moving from its legacy JWT-based keys to a new key format, and the sb_publishable key replaces the anon key while sb_secret keys replace the service_role key. The sb_publishable key, prefixed sb_publishable, is the public, client-side key, playing the same role the anon key did: safe to ship in your frontend and governed by Row Level Security. The sb_secret keys, prefixed sb_secret, are the secret, server-side keys that replace the service_role key, and you can create multiple of them.
The migration matters because the legacy anon and service_role keys are being deprecated, with support ending by the close of 2026, so you should move to the new keys rather than wait. The security roles are unchanged: the publishable key is public and constrained by your policies, and the secret keys bypass Row Level Security and must stay server-side, exactly as the anon and service_role keys did. What changes is the format, that they are no longer JWTs, and how you send them, which is the source of the errors people hit during migration.
Why you get JWT signature errors
The Invalid JWT error after switching to the new keys comes from a mismatch between what the key is and how it is sent, not from a broken key. The legacy anon and service_role keys were JWTs, so Supabase clients send them on the Authorization Bearer header, where the platform parses and verifies them as JWTs. The new sb_publishable and sb_secret keys are not JWTs, so when a client sends one on that same Authorization Bearer header, the platform tries to parse it as a JWT, fails, and rejects the request as an Invalid JWT.
The fix is to send the new key on the apikey header instead of, or rather than as, an Authorization Bearer token, since the new keys are meant to travel on the apikey header. Many Supabase client libraries pass the key on the Authorization header by default, which is what triggers the error after migration, so you correct it in how you configure the client. For Edge Functions specifically, there is a second lever: deploying the function so the platform does not attempt JWT verification at all, which is where the no-verify-jwt option comes in.
What no-verify-jwt actually does
The no-verify-jwt option, used when you deploy an Edge Function, tells the Supabase platform not to verify a JWT on requests to that function. Normally, the platform checks the incoming request for a valid JWT before your function runs, acting as a built-in authentication gate. With no-verify-jwt set, that check is skipped, so the function can be invoked without the platform validating any JWT, which is necessary in some setups using the new keys because the platform does not JWT-verify the apikey header the way it did the legacy JWT keys.
It is important to understand what this does and does not do. It removes the platform's JWT check, so a request no longer needs a valid JWT to reach your function code. It does not remove any authentication you perform inside the function, and it does not make the function secure or insecure by itself; it changes who is allowed to reach your code. So no-verify-jwt is a switch that moves the responsibility for authentication from the platform to you, which is fine when you know that and handle it, and dangerous when you set it to silence an error without realizing the gate is now open.
Is no-verify-jwt safe?
Whether no-verify-jwt is safe depends entirely on what the function does and whether you add your own authentication. It is safe for a function that is genuinely meant to be public, such as a webhook receiver or an endpoint that returns only public data, because there is nothing to protect and the platform gate was not doing security work for it. It is also safe for a protected function when you authenticate the request inside the function yourself, verifying the user and their permissions in your code rather than relying on the platform.
It is unsafe when you use no-verify-jwt to make a JWT error go away on a function that performs privileged operations or returns sensitive data, without adding your own check, because you have removed the authentication gate and left the function open to anyone who can call it. That is the anti-pattern to avoid: disabling verification to fix an error, and thereby exposing an operation that needed authentication. So before setting no-verify-jwt, ask whether the function is public or whether you will authenticate inside it; if neither is true, do not use it as an error fix, and instead correct how the key is sent so verification succeeds.
The safe migration path
The safe way through this migration is to change how you send the keys, not to disable security. Adopt the sb_publishable key in your client and the sb_secret keys on your server, keeping the same public-versus-secret discipline as before, and send the new keys on the apikey header so the platform does not misinterpret them as JWTs. This alone resolves most Invalid JWT errors without touching your functions' verification.
For Edge Functions, decide each function's exposure deliberately. Leave the platform JWT verification on where it fits your key setup, and where you must use no-verify-jwt, treat it as a decision to handle authentication yourself: for public functions, confirm they truly expose nothing sensitive, and for protected ones, add explicit authentication and authorization in the function code, verifying the caller and applying your access rules. Keep the sb_secret keys strictly server-side, never in a client, since they bypass Row Level Security just as the service_role key did. Migrating this way fixes the errors while preserving, rather than removing, your authentication.
Errors and fixes
Matching each symptom to its cause and fix keeps the migration from turning into a security mistake. The table below pairs them.
| Symptom | Cause | Fix |
|---|---|---|
| Invalid JWT with the new key | Key sent on Authorization Bearer and parsed as a JWT | Send the new key on the apikey header |
| Edge Function rejects the request | Platform JWT verification does not accept the new key | Deploy with no-verify-jwt and authenticate in the function |
| Function open to anyone | no-verify-jwt set with no in-function auth | Add authentication inside the function or re-enable verification |
| service_role or sb_secret in client | Secret key used where a public key belongs | Move it server-side and use sb_publishable in the client |
Read the table by whether the fix touches security: sending the key correctly is a config fix, while no-verify-jwt shifts authentication to you and requires you to actually do it.
Migration checklist
Working through these steps migrates safely. The checklist below covers them.
| Step | Action | Done? |
|---|---|---|
| Adopt the new keys | Use sb_publishable in the client, sb_secret on the server | [ ] |
| Fix the header | Send the new keys on the apikey header, not Authorization Bearer | [ ] |
| Decide function exposure | Public, or protected with your own auth | [ ] |
| Handle no-verify-jwt functions | Add authentication inside any that need it | [ ] |
| Keep secrets server-side | sb_secret never ships in the client | [ ] |
| Test each function | Confirm it works and protected ones stay protected | [ ] |
The step that prevents a security hole is handling authentication in any function you deploy with no-verify-jwt, since that option removes the platform's gate and leaves protection up to you.
Scan your app after migrating
Because the migration changes which keys your app carries, confirming that only the public key ships is worth doing afterward, especially if an AI builder or a copied snippet wired up the wrong one.
A scanner like PTKD.com analyzes your app build and reports issues such as leaked keys and secrets, insecure data storage, and over-broad permissions by severity, mapped to OWASP MASVS, so an sb_secret or service_role key that ended up in the client is flagged, while the sb_publishable key is expected. To be clear about the boundary: PTKD does not configure your Edge Functions or verify your in-function authentication, which you handle in Supabase. It confirms your shipped app carries only the public key, not a secret that would bypass your Row Level Security.
What to take away
- Supabase's sb_publishable key replaces the anon key and sb_secret keys replace the service_role key, with the legacy JWT keys deprecated by the end of 2026.
- The Invalid JWT error after migrating happens because the new keys are not JWTs but are sent on the Authorization Bearer header, so send them on the apikey header instead.
- The no-verify-jwt option stops the platform from JWT-verifying an Edge Function, moving the responsibility for authentication from the platform to you.
- no-verify-jwt is safe for genuinely public functions or when you authenticate inside the function, and unsafe when used to silence an error on a function that needs protection.
- Migrate by fixing the header and handling function auth deliberately, keep sb_secret keys server-side, and scan your app with a tool like PTKD.com.




