What is being deprecated#
The four framework adapters that ship inside @supabase/server:
@supabase/server/adapters/hono@supabase/server/adapters/h3@supabase/server/adapters/elysia@supabase/server/adapters/nestjs
They still work today. They will be removed from the package on December 1, 2026. No new adapters are accepted.
Nothing else changes. withSupabase(config, handler) is not deprecated. Neither is its entry form, withSupabase(config), nor any of the @supabase/server/middleware/* entries.
Why#
Each adapter wrapped one call, withSupabase, for one framework. That meant a published entry point, a peer dependency range, and a release cycle per framework. @supabase/server has run on the @supabase/middleware engine since 1.5.1, and the engine replaced the model. A middleware is an entry you compose. The piece that is framework-specific is a short bridge, and that file belongs in your project, not in a package that has to track your framework's major versions.
Timeline#
The @supabase/server README has carried the deprecation warning since 1.9.0. From 1.9.1, every adapter export carries an @deprecated tag and each adapter doc opens with the same warning. The adapters are removed on December 1, 2026.
What to migrate to#
A bridge: one file you copy into your project. It runs a @supabase/middleware entry array inside your framework's own middleware slot. Your route handlers then read supabase, jwtClaims, and any other key the entries contribute from where your framework keeps per-request state. Bridges exist for Hono, H3, Elysia, NestJS, and TanStack Start, and they typecheck in CI.
The bridges need @supabase/server 1.6.0 or later and Node 22 or later. The bridge file imports @supabase/middleware, so add it to your project as a direct dependency.
The frameworks guide has the bridge for each framework, the full migration steps, and a prompt you can hand to a coding agent to do the migration for you.
Migration steps#
- Inventory every adapter registration. Grep for
@supabase/server/adaptersand note theauthvalue eachwithSupabase(...)call uses. A barewithSupabase()counts asauth: 'user'. - Decide the replacement for each
authvalue.auth: 'user'becomeswithRequiredClaims().auth: 'none'needs no gate.auth: 'publishable'andauth: 'secret'have no bridge entry; those endpoints stay onwithSupabase. - Do not use
withClaims()in place ofauth: 'user'. The adapter rejected anonymous requests.withClaims()lets them through withjwtClaims: null, which turns a 401 into a 200 with no error. - Add
@supabase/middlewareas a direct dependency, then copy the bridge for your framework fromexamples/frameworksas is, comments included. - Swap each registration for the bridge called on an entry array, for example
toHono([withRequiredClaims(), withSupabaseClient()]). - Rewrite the call sites. The adapters exposed one nested
supabaseContextobject. The entries contribute flat keys:c.var.supabase,c.var.jwtClaims. No entry contributesuserClaims.userClaims.idbecomesjwtClaims.sub. - Verify. Run the typecheck. Then confirm that every endpoint that rejected anonymous requests before still returns 401 without a token.