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

    Function defineMiddleware

    • Defines a middleware.

      run sees the inbound Request and the upstream context. It either short-circuits with a Response, or contributes a value by returning { [key]: contribution }; the framework merges result[key] into the context and calls the inner handler.

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

      Response seam. When a middleware needs to see the way out (stamp headers, time the request, run finally cleanup), write run as an async function* instead of async. Code before yield is the request phase; yielding hands back the contribution, and the yield expression resolves to the downstream Response for the response phase, the only place a middleware observes the handler's response. Short-circuit the same way as the request-side path, with a plain return new Response(...), and yield at most once.

      Composition. withFoo(config, handler) produces a single (req, ctx) => Response function. Middleware nest directly, and the outermost one is used as the runtime's fetch handler with no wrapper: export default { fetch: withFoo(config, handler) }. The host's second argument is a platform value (a Workers env, a Deno ServeHandlerInfo), not an upstream context. isContext detects this 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 getEnv.

      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>

      Extra keys on the returned object are ignored at runtime and not caught at compile time, since run's return is contextually typed against a Response | { [key]: … } union via a generic mapped type, a position where TypeScript suppresses excess-property checks. The contributionOf guard is the runtime backstop: it throws only when the key is missing entirely (e.g. a computed or typo'd key the types could not see). Annotate run's inner return type explicitly to catch excess keys at compile time instead.

      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 nest inside a wrapper (not necessarily the immediate one) that supplies those keys, never as the bare fetch export, since that would make the prerequisite a lie. An unmet prerequisite propagates outward until something supplies it; if nothing does, the stack keeps a required ctx, which fails only where it is checked against FetchHandler. Annotate the outermost call to catch this at build time; an untyped export default { fetch: … } will not catch it.
      • 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, same as satisfies FetchHandler would seed), and the cascade proceeds inward.
      • Collision detection does need one. Composing where the upstream already has the key resolves the handler parameter to a Conflict sentinel (see NoConflict), but only under satisfies FetchHandler on the outermost call: the produced type records what a stack requires, not what it contributes, so an unannotated call has nothing to check a duplicate key against, and it compiles silently. One annotation covers any depth. pipeline has no such gap, since it validates from its entries array; a stack that cannot carry the annotation is better written flat.
      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 } }
      },
      })