> Discover all available pages from the documentation index: https://mastra.ai/llms.txt

# Middleware

Mastra servers can execute custom middleware functions before or after an API route handler is invoked. This is useful for things like authentication, logging, injecting request-specific context or adding CORS headers.

A middleware receives the [Hono](https://hono.dev) `Context` (`c`) and a `next` function. If it returns a `Response` the request is short-circuited. Calling `next()` continues processing the next middleware or route handler.

```typescript
import { Mastra } from '@mastra/core'

export const mastra = new Mastra({
  server: {
    middleware: [
      {
        handler: async (c, next) => {
          // Example: Add authentication check
          const authHeader = c.req.header('Authorization')
          if (!authHeader) {
            return new Response('Unauthorized', { status: 401 })
          }

          await next()
        },
        path: '/api/*',
      },
      // Add a global request logger
      async (c, next) => {
        console.log(`${c.req.method} ${c.req.url}`)
        await next()
      },
    ],
  },
})
```

To attach middleware to a single route pass the `middleware` option to `registerApiRoute`:

```typescript
registerApiRoute('/my-custom-route', {
  method: 'GET',
  middleware: [
    async (c, next) => {
      console.log(`${c.req.method} ${c.req.url}`)
      await next()
    },
  ],
  handler: async c => {
    const mastra = c.get('mastra')
    return c.json({ message: 'Hello, world!' })
  },
})
```

## Common examples

### Using `RequestContext`

You can populate `RequestContext` dynamically in server middleware by extracting information from the request. In this example, the `temperature-unit` is set based on the Cloudflare `CF-IPCountry` header to ensure responses match the user's locale.

```typescript
import { Mastra } from '@mastra/core'
import { RequestContext } from '@mastra/core/request-context'
import { testWeatherAgent } from './agents/test-weather-agent'

export const mastra = new Mastra({
  agents: { testWeatherAgent },
  server: {
    middleware: [
      async (context, next) => {
        const country = context.req.header('CF-IPCountry')
        const requestContext = context.get('requestContext')

        requestContext.set('temperature-unit', country === 'US' ? 'fahrenheit' : 'celsius')

        await next()
      },
    ],
  },
})
```

### Authentication

```typescript
{
  handler: async (c, next) => {
    const authHeader = c.req.header('Authorization');
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      return new Response('Unauthorized', { status: 401 });
    }

    // Validate token here
    await next();
  },
  path: '/api/*',
}
```

### Authorization (User Isolation)

Authentication verifies who the user is. Authorization controls what they can access. Without resource ID scoping, an authenticated user could access other users' threads by guessing IDs or manipulating the `resourceId` parameter.

The simplest way to scope memory and threads to the authenticated user is the `mapUserToResourceId` callback in the auth config:

```typescript
import { Mastra } from '@mastra/core'

export const mastra = new Mastra({
  server: {
    auth: {
      authenticateToken: async token => {
        return verifyToken(token) // { id: 'user-123', orgId: 'org-456', ... }
      },
      mapUserToResourceId: user => user.id,
    },
  },
})
```

After successful authentication, `mapUserToResourceId` is called with the authenticated user object. The returned value is set as `MASTRA_RESOURCE_ID_KEY` on the request context, which works across all server adapters (Hono, Express, Next.js, etc.).

The resource ID doesn't have to be `user.id`. Common patterns:

```typescript
// Org-scoped
mapUserToResourceId: user => `${user.orgId}:${user.id}`

// From a JWT claim
mapUserToResourceId: user => user.tenantId

// Composite key
mapUserToResourceId: user => `${user.workspaceId}:${user.projectId}:${user.id}`
```

With a resource ID set, the server automatically:

- **Filters thread listing** to only return threads owned by the user
- **Validates thread access** and returns 403 if accessing another user's thread
- **Forces thread creation** to use the authenticated user's ID
- **Validates message operations** including deletion, ensuring messages belong to owned threads

Even if a client passes `?resourceId=other-user-id`, the auth-set value takes precedence. Attempts to access threads or messages owned by other users will return a 403 error.

#### Advanced: Setting resource ID in middleware

For more complex scenarios (e.g., looking up the resource ID from a database), you can set `MASTRA_RESOURCE_ID_KEY` directly in middleware:

```typescript
import { Mastra } from '@mastra/core'
import { MASTRA_RESOURCE_ID_KEY } from '@mastra/core/request-context'
import { getAuthenticatedUser } from '@mastra/server/auth'

export const mastra = new Mastra({
  server: {
    auth: {
      authenticateToken: async token => verifyToken(token),
    },
    middleware: [
      {
        path: '/api/*',
        handler: async (c, next) => {
          const token = c.req.header('Authorization')
          if (!token) {
            return c.json({ error: 'Unauthorized' }, 401)
          }

          const user = await getAuthenticatedUser<{ id: string }>({
            mastra: c.get('mastra'),
            token,
            request: c.req.raw,
          })
          const requestContext = c.get('requestContext')

          if (!user) {
            return c.json({ error: 'Unauthorized' }, 401)
          }

          requestContext.set(MASTRA_RESOURCE_ID_KEY, user.id)
          return next()
        },
      },
    ],
  },
})
```

`server.middleware` runs before Mastra's per-route auth checks. When middleware needs the authenticated user, call `getAuthenticatedUser()` to resolve it from the configured auth provider without changing the default route auth flow.

#### Using `MASTRA_THREAD_ID_KEY`

You can also set `MASTRA_THREAD_ID_KEY` to override the client-provided thread ID:

```typescript
import { MASTRA_RESOURCE_ID_KEY, MASTRA_THREAD_ID_KEY } from '@mastra/core/request-context'

// Force operations to use a specific thread
requestContext.set(MASTRA_THREAD_ID_KEY, validatedThreadId)
```

This is useful when you want to restrict operations to a specific thread that you've validated through other means.

### CORS support

```typescript
{
  handler: async (c, next) => {
    c.header('Access-Control-Allow-Origin', '*');
    c.header(
      'Access-Control-Allow-Methods',
      'GET, POST, PUT, DELETE, OPTIONS',
    );
    c.header(
      'Access-Control-Allow-Headers',
      'Content-Type, Authorization',
    );

    if (c.req.method === 'OPTIONS') {
      return new Response(null, { status: 204 });
    }

    await next();
  },
}
```

### Request logging

```typescript
{
  handler: async (c, next) => {
    const start = Date.now();
    await next();
    const duration = Date.now() - start;
    console.log(`${c.req.method} ${c.req.url} - ${duration}ms`);
  },
}
```

# Related

- [Request Context](https://mastra.ai/docs/server/request-context)
- [Reserved Keys](https://mastra.ai/docs/server/request-context)