Framework adapters in @supabase/server are deprecated

Oct 5, 2026

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#

  1. Inventory every adapter registration. Grep for @supabase/server/adapters and note the auth value each withSupabase(...) call uses. A bare withSupabase() counts as auth: 'user'.
  2. Decide the replacement for each auth value. auth: 'user' becomes withRequiredClaims(). auth: 'none' needs no gate. auth: 'publishable' and auth: 'secret' have no bridge entry; those endpoints stay on withSupabase.
  3. Do not use withClaims() in place of auth: 'user'. The adapter rejected anonymous requests. withClaims() lets them through with jwtClaims: null, which turns a 401 into a 200 with no error.
  4. Add @supabase/middleware as a direct dependency, then copy the bridge for your framework from examples/frameworks as is, comments included.
  5. Swap each registration for the bridge called on an entry array, for example toHono([withRequiredClaims(), withSupabaseClient()]).
  6. Rewrite the call sites. The adapters exposed one nested supabaseContext object. The entries contribute flat keys: c.var.supabase, c.var.jwtClaims. No entry contributes userClaims. userClaims.id becomes jwtClaims.sub.
  7. Verify. Run the typecheck. Then confirm that every endpoint that rejected anonymous requests before still returns 401 without a token.

Build in a weekend, scale to millions