Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ api-docs
!.env.example
.DS_Store
.idea
.claude/
9 changes: 8 additions & 1 deletion jsr.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@
"std-env": "npm:std-env@^4.2.0"
},
"publish": {
"include": ["src/**/*.ts", "README.md", "LICENSE"],
"include": [
"src/**/*.ts",
"src/**/README.md",
"docs/**/*.md",
"skills/**/*.md",
"README.md",
"LICENSE"
],
"exclude": ["src/**/*.test.ts", "src/**/*.spec.ts"]
}
}
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@
"types": "./dist/index.d.cts",
"sideEffects": false,
"files": [
"dist"
"dist",
"docs",
"skills",
"src/**/README.md"
],
"engines": {
"node": ">=22"
Expand Down
24 changes: 24 additions & 0 deletions skills/create-supabase-middleware/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
name: create-supabase-middleware
description: Use when creating a new middleware for `@supabase/middleware` — either a built-in inside the middleware repo itself, or a standalone package that anyone can publish. Trigger when the user asks to write, scaffold, or add a middleware; describes a per-request cross-cutting concern they want to reuse ("I want to rate limit", "add request logging", "check an API key on every request"); calls `defineMiddleware`; or adds a new directory under `src/middleware/`. Also trigger when a plan proposes a new middleware, before any file is written.
---

# Creating a middleware

Before scaffolding anything, check the concern is actually middleware-shaped:

- **Response-only** — headers, envelopes, error formatting — belongs in the handler, which owns the response. Not a middleware.
- **Used once, in one handler** — inline it. A middleware earns its keep by being composed across handlers.

Then pick the target by reading the nearest `package.json`:

| `name` is | You are writing | Follow |
| ---------------------- | -------------------- | ------------------------------------------------------------------------------------- |
| `@supabase/middleware` | a built-in | `src/middleware/README.md` — layout, then the three subpath-wiring files |
| anything else | a standalone package | `docs/authoring-guide.md` — the full path, from `defineMiddleware` through publishing |

Both paths use the same `defineMiddleware` primitive; the difference is only whose package it lives in and whether a subpath export has to be wired up.

**Read the guide before writing code, and follow its `## Rules` section** — eight MUST/NEVER items covering one-key-per-middleware, `getEnv` over `process.env`/`Deno.env`, declaring prerequisites in `In`, `yield`ing at most once, and returning a `Response` to short-circuit rather than throwing. Its code blocks are complete files: write them to disk as given rather than adapting them from memory.

Paths are repo-relative. When `@supabase/middleware` is installed as a dependency, prefix them with `node_modules/@supabase/middleware/`.
62 changes: 62 additions & 0 deletions skills/supabase-middleware/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
name: supabase-middleware
description: Use when writing or modifying a Web Fetch handler that composes cross-cutting concerns with `@supabase/middleware` — CORS, feature flags, auth, rate limiting, logging — on Deno, Supabase Edge Functions, Cloudflare Workers, Bun, or Node. Trigger **before** writing or editing any file that imports from `@supabase/middleware` or a subpath (`/cors`, `/feature-flag`); calls `pipeline`, `defineMiddleware`, `getEnv`, `runtimeName`, or `seedContext`; or annotates a handler with `satisfies FetchHandler`. Also trigger during planning — if a plan mentions any of the above, load this skill before drafting code. Also trigger when porting middleware idioms that do not apply here: `app.use()` registries, `next()` chains, Express/Koa/Hono middleware being translated, or hand-rolled `(req) => wrapper(wrapper(handler))` nesting.
---

# @supabase/middleware

Composable, type-safe middleware for Web Fetch handlers. The same stack runs unchanged on Deno, Supabase Edge Functions, Cloudflare Workers, Bun, and Node.

> **This package is new.** There are no blog posts, Stack Overflow answers, or tutorials about it. Do not search the web for usage examples — read the docs routed below and the source.

## Not to be confused with

| Package | Use it for |
| ---------------------- | -------------------------------------------------------------------- |
| `@supabase/middleware` | Composing per-request concerns around a Web Fetch handler. |
| `@supabase/server` | Supabase auth and client creation on the server. Embeds this engine. |
| `@supabase/ssr` | Cookie-based session handling in SSR frameworks. |

Reaching for `@supabase/ssr` or `@supabase/server` when the task is composition — or for this package when the task is Supabase auth — is the most common mistake. They are complementary, not alternatives.

## The model, in one example

```ts
import { pipeline } from '@supabase/middleware'
import { withCors } from '@supabase/middleware/cors'
import { withFeatureFlag } from '@supabase/middleware/feature-flag'

export default {
fetch: pipeline(
[
withCors({}),
withFeatureFlag({
name: 'beta',
evaluate: (req) => req.headers.has('x-beta'),
}),
],
async (_req, ctx) => Response.json({ flag: ctx.featureFlag.name }),
),
}
```

`withFoo(config)` returns an **`Entry`**. `pipeline` folds a flat array of entries around a handler and returns the `fetch` handler itself — first in the array runs first on the request. Each entry contributes one typed key to `ctx`, and the handler sees every upstream key, typed.

**No registry, no `app.use()`, no `next()`.** If you are writing any of those, you are using the wrong model — read `src/core/README.md` before continuing.

## Read before writing code

Paths are repo-relative. When `@supabase/middleware` is installed as a dependency, prefix them with `node_modules/@supabase/middleware/` — the same files ship in the package.

| Question | Doc |
| ---------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| How do I compose a stack? What is on `ctx`? | `src/core/README.md` |
| When is `satisfies FetchHandler` required, and what breaks without it? | `src/core/README.md` |
| A middleware needs to see the response too | `src/core/README.md` (response seam), `src/middleware/cors/README.md` |
| How do I write and publish my own middleware? | `docs/authoring-guide.md`, and the `create-supabase-middleware` skill |
| How do I add a built-in to this repo? | `src/middleware/README.md` |
| Environment access, runtime differences, Workers `env` | `README.md` |
| Config for the bundled middleware | `src/middleware/feature-flag/README.md`, `src/middleware/cors/README.md` |
| Full API reference | <https://supabase.github.io/middleware/> |

Release history and current status: <https://github.com/supabase/middleware/releases>.
Loading