# Function Middleware for Server Functions

> Configure authentication and authorization middleware for TanStack Start server functions in your Supabase SaaS application.

*Canonical: https://makerkit.dev/docs/tanstack-supabase/development/function-middleware*

---

Server logic in this kit runs through **TanStack Start server functions**
(`createServerFn`). Authentication and authorization are composed from reusable
middleware living in the `@kit/function-middleware` package, so individual
functions never hand-roll auth checks.

## Package shape

`@kit/function-middleware` exposes two entrypoints:

- **`@kit/function-middleware/functions`** — pre-composed middleware **tuples** for
  the function definition site:
  - `authFunctionMiddleware` — requires an authenticated user
  - `teamAccountFunctionMiddleware` — requires membership on the `accountId` in the input
  - `adminFunctionMiddleware` — requires a Super Admin (`is_superadmin`)
- **`@kit/function-middleware/server`** — the lower-level middleware and gates the
  tuples compose: `errorMiddleware`, `authMiddleware`, `adminMiddleware`,
  `teamAccountMiddleware`, `withMinRole(role)`, and
  `withFeaturePermission(permission)`.

Each tuple always starts with `errorMiddleware`, which normalizes thrown errors to
a plain message (so raw Postgrest/internal error objects are never serialized to
the browser) while letting `redirect()` / `notFound()` pass through untouched.

## Rules

1. Keep `createServerFn(...)` **literal** at the function definition site. A
   factory that returns a `createServerFn` builder hides the function from the
   TanStack Start compiler, shipping the handler — and its Supabase/server graph —
   to the client bundle.
2. Put server functions in `*.functions.ts` files.
3. Suffix each exported server function with `Function`.
4. Use the tuple middleware instead of hand-written auth checks.
5. Call server functions from the client with `useServerFn(...)`.

## Authenticated function

```ts
import { createServerFn } from '@tanstack/react-start';
import { authFunctionMiddleware } from '@kit/function-middleware/functions';

export const updateAccountNameFunction = createServerFn({ method: 'POST' })
  .middleware(authFunctionMiddleware)
  .validator(UpdateAccountNameSchema)
  .handler(async ({ data, context }) => {
    // context.user is available
  });
```

## Team-account function

`teamAccountFunctionMiddleware` validates a uuid `accountId` in the input and
asserts membership via the `has_role_on_account` RPC. The function's `.validator`
must therefore include an `accountId` field.

```ts
import { createServerFn } from '@tanstack/react-start';
import { teamAccountFunctionMiddleware } from '@kit/function-middleware/functions';

export const updateTeamSettingsFunction = createServerFn({ method: 'POST' })
  .middleware(teamAccountFunctionMiddleware)
  .validator(UpdateTeamSettingsSchema) // includes `accountId`
  .handler(async ({ data, context }) => {
    // context.user, context.accountId
  });
```

## Admin function

```ts
import { createServerFn } from '@tanstack/react-start';
import { adminFunctionMiddleware } from '@kit/function-middleware/functions';

export const banUserFunction = createServerFn({ method: 'POST' })
  .middleware(adminFunctionMiddleware)
  .validator(BanUserSchema)
  .handler(async ({ data, context }) => {
    // context.user is a verified Super Admin
  });
```

## Extra gates

Layer role or permission gates by spreading a tuple. Both gates read `accountId`
from the input, so keep it in the `.validator`.

```ts
import { authFunctionMiddleware } from '@kit/function-middleware/functions';
import {
  withFeaturePermission,
  withMinRole,
} from '@kit/function-middleware/server';

// Require a minimum role in the account's role hierarchy:
export const deleteTeamAccountFunction = createServerFn({ method: 'POST' })
  .middleware([...authFunctionMiddleware, withMinRole('owner')])
  .validator(DeleteTeamAccountSchema)
  .handler(async ({ data, context }) => { ... });

// Require a specific feature permission (checked via the `has_permission` RPC):
export const removeMemberFunction = createServerFn({ method: 'POST' })
  .middleware([...authFunctionMiddleware, withFeaturePermission('members.manage')])
  .validator(RemoveMemberSchema)
  .handler(async ({ data, context }) => { ... });
```

These gates are defense-in-depth: Row Level Security still enforces access in the
service/data layer.

## Client calls

Call server functions from client components with `useServerFn(...)`, then drive
them through a TanStack Query mutation:

```tsx
import { useMutation } from '@tanstack/react-query';
import { useServerFn } from '@tanstack/react-start';

import { updateAccountNameFunction } from '../server/account.functions';

function UpdateNameForm() {
  const updateName = useServerFn(updateAccountNameFunction);

  const mutation = useMutation({
    mutationFn: (data: FormValues) => updateName({ data }),
  });

  // derive UI from mutation.isPending / onSuccess / onError
}
```

Keep request-lifecycle state in the mutation and business logic in the server
function/service — the client only orchestrates the call and the resulting UI.
