---
slug: supabase-middleware-1-0
published: 2026-09-30
change_type: new-feature
affected_products:
  - Edge Functions
  - supabase-js
  - supabase-middleware
  - supabase-server
page: https://supabase.com/changelog/supabase-middleware-1-0
---

# Supabase Middleware 1.0

## What's new

`@supabase/middleware` 1.0.0 is on npm and JSR under the `latest` tag. From 1.0 the package follows semantic versioning: breaking changes ship only in a new major.

It is a small, MIT-licensed engine for per-request logic on Web Fetch handlers:

- `defineMiddleware` writes one piece of per-request logic. It contributes one typed key to a shared `ctx`.
- `pipeline([...], handler)` runs several in order and returns the fetch handler. Middleware also nest directly, so `pipeline` is optional.
- The handler sees every upstream key, correctly typed. Duplicate keys fail to compile, and so does a middleware placed before its prerequisite.
- Any middleware can return a `Response` and stop the chain, or read and change the response on the way out.
- Two middleware ship with the engine: `withCors` and `withFeatureFlag`. Everything else is yours, or comes from a package.

The engine runs wherever fetch runs: Supabase Edge Functions, Vercel Functions, Cloudflare Workers, Deno, Bun, and Node 22 or newer. Inside Hono, H3, Elysia, NestJS, or TanStack Start, the same middleware runs through a short, typechecked bridge you copy into your app. No adapter package, no new dependency. See the [frameworks guide](https://supabase.com/docs/reference/server/frameworks).

`@supabase/server` is built on the engine and ships the Supabase middleware. `withSupabase(config)` with no handler is a `pipeline` entry; `withSupabase(config, handler)` wraps a single handler. Entries before it in the array run ahead of the auth gate, and entries after it receive the Supabase context. The `@supabase/server/middleware/*` subpaths export the pieces on their own: `withClaims`, `withRequiredClaims`, `withSupabaseClient`, `withSupabaseAdminClient`, `withPostgresClient`, and `withPostgresAdminClient`. `withOAuthProtectedResource` answers OAuth discovery for MCP clients. All of them are stable as of `@supabase/server` 1.9.0.

## How to use it

```sh
npm install @supabase/middleware
```

On Edge Functions, import it directly. This example answers OAuth discovery for MCP clients, verifies the caller's token, and hands the handler a Supabase client scoped to that user. The [Bring your own MCP guide](https://supabase.com/docs/guides/ai-tools/byo-mcp) builds a full MCP server on the same two entries:

```ts
import { pipeline } from 'npm:@supabase/middleware@1'
import { withOAuthProtectedResource, withSupabase } from 'npm:@supabase/server@1'

Deno.serve(
  pipeline(
    // 1. OAuth discovery for MCP clients, 2. verify the user's token and scope a client to them
    [withOAuthProtectedResource(), withSupabase({ auth: 'user' })],
    async (req, { supabase }) => {
      // RLS scopes this query to the signed-in user
      const { data } = await supabase.from('tasks').select('id, title')
      return Response.json(data)
    },
  ),
)
```

Writing your own is one call:

```ts
import { defineMiddleware } from '@supabase/middleware'

const withRequestId = defineMiddleware<'requestId', void, Record<never, never>, string>({
  key: 'requestId',
  run: () => async (req) => ({
    requestId: req.headers.get('x-request-id') ?? crypto.randomUUID(),
  }),
})
```

The [authoring guide](https://github.com/supabase/middleware/blob/main/docs/authoring-guide.md) covers prerequisites, the response seam, bundling several middleware into one, and publishing to npm and JSR.

## Why we built this

Per-request logic is stuck where you wrote it. Move to a different runtime and you write it again. Adopt a framework and you rewrite it as that framework's middleware, which cannot leave that framework. Start a second project and you copy the file across. This is the code least worth rewriting: verifying a caller, scoping a database connection to them, handling CORS, checking a flag. It is fiddly, security-sensitive, and easy to get subtly wrong the second time.

`@supabase/server` has run on this engine since 1.5.1, released on August 31, 2026. `withSupabase` is a composite of single-key parts: the auth gate, claims, CORS, the Supabase client, the admin client. `withOAuthProtectedResource` handles the whole OAuth protected-resource flow an MCP client expects, and it is one entry in the pipeline. None of it uses a private API. What you get is the same primitive we use.
