@supabase/middleware - v0.4.0
    Preparing search index...

    Function defineMiddleware

    • Defines a middleware.

      A middleware runs against an inbound Request and the upstream context. It either short-circuits by returning a Response, or contributes a typed value at ctx[key] by returning a single-key object { [key]: contribution } — the framework picks result[key], merges it into the context, and calls the inner handler. Extra keys on the returned object are ignored at runtime. Note they are not caught at compile time: run's return is contextually typed against a Response | { [key]: … } union via a generic mapped type, a position where TypeScript suppresses excess-property checks — so a stray sibling key slips past the types (harmlessly). The runtime contributionOf guard is the backstop, and it throws only when the key is missing entirely (e.g. a computed/typo'd key the types couldn't see). To opt into the excess check on a given middleware, annotate run's inner return type explicitly.

      run is request-side by default (the common case): it runs before the handler and never observes the handler's Response. Response-shaped concerns (CORS, envelopes) normally belong in the handler or a .then() on the stack.

      Response seam (escape hatch). When a middleware genuinely needs to see the way out — stamp headers, time the request, run finally cleanup — write run as an async function* instead of async. yield is the seam: code before it is the request phase; you yield the contribution and the yield expression resolves to the downstream Response for the response phase. yield always means "run downstream and hand me the response" — short-circuit with a plain return new Response(...), exactly as the request-side path does, and yield at most once. This is the one place a middleware observes the handler's response, and writing function* is the visible, opt-in signal.

      withFoo(config, handler) produces a single (req, ctx) => Response function. Middleware nest directly, and the outermost is used as the runtime's fetch handler with no wrapperexport default { fetch: withFoo(config, handler) }. When the host invokes it, the second argument is a platform value (a Workers env, a Deno ServeHandlerInfo), not an upstream context; the wrapper detects this via isContext and seeds a fresh context instead of merging it, so platform arguments never leak into ctx — they are only captured as the module-scoped platform env behind the importable getEnv.

      Typing:

      • Prerequisite-free middleware can be the fetch export on their own. Their produced handler has an optional ctx, so it satisfies a bare (req) => Response export and self-seeds a fresh context.
      • Middleware with In prerequisites require ctx. They can only be nested inside a wrapper that supplies those keys — never the bare fetch export — which keeps the prerequisite from being a type-lie at the top level. The supplying wrapper need not be the immediately enclosing one, and no annotation is needed: an unmet prerequisite is republished by each layer that doesn't contribute it, travelling outward until one does. If none does, the stack keeps a required ctx and fails wherever it is checked against FetchHandler — but an untyped export default { fetch: … } is no such check, so annotate the outermost call to catch it at build time.
      • Accumulation needs no annotation. The innermost handler sees every upstream key ambiently at any nesting depth. An unannotated outermost call has no contextual return type, so Base resolves to its constraint — the empty upstream, which is exactly what a satisfies FetchHandler would seed — and the cascade proceeds inward from there.
      • Collision detection does. Composing where the upstream already has the key resolves the handler parameter to a Conflict sentinel (see NoConflict) and the stack fails to typecheck — but only under satisfies FetchHandler on the outermost call. The produced handler type records the upstream a stack requires, never the keys it contributes, so an unannotated enclosing call has nothing to check its own key against and a duplicate compiles silently. One annotation covers any depth. pipeline has no such gap — it validates from its entries array — so a stack that cannot carry the annotation is better written flat.

      Type Parameters

      • const Key extends string

        The literal-string key contributed to ctx.

      • Config

        Configuration object the middleware accepts.

      • In extends object = Record<never, never>

        Upstream prerequisites besides BaseContext. Defaults to none.

      • Contribution = unknown

        Shape of the value placed at ctx[Key].

      Parameters

      • spec: {
            key: Key;
            run: (
                config: Config,
            ) => (
                req: Request,
                ctx: In,
            ) =>
                | Promise<Response | { [K in string]: Contribution }>
                | AsyncGenerator<
                    Response
                    | { [K in string]: Contribution },
                    void | Response,
                    Response,
                >;
        }

      Returns Middleware<Key, Config, In, Contribution>

      import { defineMiddleware } from '@supabase/middleware'

      export const withFeatureFlag = defineMiddleware<
      'featureFlag',
      { name: string; evaluate: (req: Request) => boolean },
      {},
      { name: string; enabled: true }
      >({
      key: 'featureFlag',
      run: (config) => async (req) => {
      if (!config.evaluate(req)) {
      return Response.json({ error: 'feature_disabled' }, { status: 404 })
      }
      return { featureFlag: { name: config.name, enabled: true } }
      },
      })