Skip to content

feat: add middleware option to withSupabase#88

Draft
mandarini wants to merge 3 commits into
mainfrom
feat/plugins-option
Draft

feat: add middleware option to withSupabase#88
mandarini wants to merge 3 commits into
mainfrom
feat/plugins-option

Conversation

@mandarini

@mandarini mandarini commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a middleware option to withSupabase so that @supabase/web-middleware entries can compose around the handler after the Supabase context is established.

Naming: the array holds middleware (per-request behavior), so that's its name. The word plugins is reserved for the package-level concept — a Plugin's middleware goes in this array, its client namespace goes in createClient({ plugins }) (supabase-js#2485). One word per concept.

import { withSupabase } from '@supabase/server'
import { withRateLimit } from '@supabase/plugin-rate-limit/server'
import { withGuestbook } from '@supabase/plugin-guestbook/server'

export default {
  fetch: withSupabase(
    { auth: 'user', middleware: [withRateLimit({ rpm: 100 }), withGuestbook()] },
    async (req, ctx) => {
      ctx.supabase      // from @supabase/server — already present when the middleware runs
      ctx.rateLimit     // from withRateLimit
      ctx.guestbook     // from withGuestbook
      return Response.json(await ctx.guestbook.list())
    },
  ),
}

The middleware receive ctx.supabase, ctx.userClaims, etc. already populated — they run after the Supabase context is created, not before. This is the key difference from wrapping withSupabase itself.

Architecture

End user
  → withSupabase({ middleware: [withRateLimit(cfg), withGuestbook()] }, handler)
  → never sees pipeline()

withSupabase internals (this PR)
  → reduceRight(middleware, handler) — same fold as pipeline()
  → only code that ever composes entries

Middleware author (e.g. withGuestbook)
  → defineMiddleware({ key, run })
  → never calls pipeline()

What changed

  • src/with-supabase.ts — two named overloads (no-middleware / with-middleware) so TypeScript can infer the handler's ctx type concretely. MiddlewareCtx<Entries> accumulates the entries' key contributions via a recursive conditional type. At runtime, entries are folded right-to-left with reduceRight; _runtime is seeded on the ctx so web-middleware entries recognise it as an upstream context (via isContext())
  • src/with-supabase.test.ts — 5 new tests: composition after Supabase context, middleware receives ctx.supabase, short-circuit before handler, array order, CORS still applied
  • package.json — adds @supabase/web-middleware as a dependency (PR build: pkg.pr.new/supabase/web-middleware/@supabase/web-middleware@9)
  • pnpm-workspace.yaml — allows @supabase/web-middleware build scripts

Try it

npm i https://pkg.pr.new/@supabase/server@88

Notes

  • The existing withSupabase(config, handler) API is unchanged — overload 1 is identical to the current signature
  • MiddlewareCtx<Entries> mirrors pipeline's internal Accumulate type without depending on the internal export
  • A full implementation would widen the prerequisite-validation seed in MiddlewareCtx to include SupabaseContext, so middleware that declare In prerequisites on supabase or userClaims get compile-time checking. Left as a follow-up (tracked in Linear SDK-1164)

Merge order

@pkg-pr-new

pkg-pr-new Bot commented Jul 1, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@supabase/server@88

commit: a1d9563

@mandarini mandarini force-pushed the feat/plugins-option branch from 0e747ac to ad449a9 Compare July 1, 2026 16:55
@mandarini mandarini changed the title feat: add plugins option to withSupabase feat: add middleware option to withSupabase Jul 2, 2026
mandarini and others added 3 commits July 3, 2026 15:43
…lution

TypeScript doesn't apply excess property checking during overload resolution,
so calls with plugins: [...] were silently matching overload 1 and typing ctx
as SupabaseContext<unknown>. Adding plugins?: never to overload 1's config
makes it definitively fail when plugins is present, falling through to the
correct overload.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The array holds middleware entries (per-request behavior from
defineMiddleware) — the word 'plugins' is reserved for the package-level
concept whose client namespace goes in createClient({ plugins }). One
word per concept: server-side composition is 'middleware', client-side
namespaces are 'plugins', a Plugin is the package that ships both.

PluginsCtx -> MiddlewareCtx; overload trick unchanged
(middleware?: never on overload 1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mandarini mandarini force-pushed the feat/plugins-option branch from 47791e0 to a1d9563 Compare July 3, 2026 12:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant