# Context

The context object `c` is passed to every route handler. It provides access to the request, locals set by `onRequest`, and validated input when a request schema is defined.

```ts
route.get("/books/:id", {
  request: { params: z.object({ id: z.uuid() }) },
  resolve: (c) => {
    c.request; // Web Standard Request
    c.input; // Validated input (body, query, params) — only when request schemas are defined
    c.locals; // Data from onRequest
  },
});
```

## c.request

**Type:** `Request`

The original Web Standard `Request` object. Use it for anything not covered by `c.input`: headers, raw body, URL, method.

```ts
resolve: (c) => {
  const auth = c.request.headers.get("authorization");
  const url = new URL(c.request.url);
  const method = c.request.method;
  const body = await c.request.text(); // raw body
},
```

## c.input

**Type:** `InputState`

Contains the result of Zod validation for body, query, and params. Only available on routes that define at least one request schema (`params`, `query`, or `body`).

### c.input.ok

**Type:** `boolean`

`true` if all schemas passed validation. `false` if any schema failed.

```ts
resolve: (c) => {
  if (!c.input.ok) {
    return Response.json({ error: c.input.issues }, { status: 400 });
  }
  // Safe to use c.input.body, c.input.query, c.input.params
},
```

### c.input.body

**Type:** Inferred from the body Zod schema, or `undefined`

The validated request body. Only available when `c.input.ok` is `true` and a body schema is defined.

```ts
route.post("/books", {
  request: { body: z.object({ title: z.string(), genre: z.string() }) },
  resolve: (c) => {
    if (!c.input.ok) return Response.json({ error: c.input.issues }, { status: 400 });
    const { title, genre } = c.input.body; // typed: { title: string, genre: string }
  },
});
```

### c.input.query

**Type:** Inferred from the query Zod schema, or `undefined`

The validated query parameters. Query strings are parsed from the URL automatically.

```ts
route.get("/books", {
  request: {
    query: z.object({ page: z.coerce.number().default(1), genre: z.string().optional() }),
  },
  resolve: (c) => {
    if (!c.input.ok) return Response.json({ error: c.input.issues }, { status: 400 });
    const { page, genre } = c.input.query; // typed: { page: number, genre?: string }
  },
});
```

### c.input.params

**Type:** Inferred from the params Zod schema

Path parameters extracted from the URL pattern, validated against the schema.

```ts
route.get("/books/:id", {
  request: { params: z.object({ id: z.uuid() }) },
  resolve: (c) => {
    if (!c.input.ok) return Response.json({ error: "Invalid ID" }, { status: 400 });
    const { id } = c.input.params; // validated UUID string
  },
});
```

### c.input.issues

**Type:** Zod issue array, or `undefined`

Validation error details when `c.input.ok` is `false`. Contains field-level error messages from Zod.

```ts
if (!c.input.ok) {
  return Response.json(
    {
      error: {
        code: "VALIDATION_ERROR",
        message: "Invalid input",
        issues: c.input.issues,
      },
    },
    { status: 400 },
  );
}
```

## c.locals

**Type:** The return type of `onRequest`

Data set by the `onRequest` callback. Use it for request IDs, authenticated user info, timing, or any per-request state.

```ts
// In setup:
onRequest: ({ request }) => {
  return { requestId: crypto.randomUUID(), userId: "user-1", startTime: Date.now() };
},

// In onResponse (locals are typed here):
onResponse: ({ response, locals }) => {
  response.headers.set("X-Request-Id", locals.requestId);
  return response;
},
```

If `onRequest` is not defined or returns `void`, `c.locals` is an empty object.

Locals are fully typed inside lifecycle hooks (`onResponse`, `onError`, `onNotFound`) because the framework infers the return type of `onRequest`. Inside route handlers, `c.locals` is available but not typed. If you need to access locals in a handler, you must provide your own types:

```ts
resolve: (c) => {
  const locals = c.locals as { requestId: string; startTime: number };
  console.log(locals.requestId);
},
```

For authentication, use a helper function that reads from `c.request` directly instead of relying on locals. See the [guide](/docs/guide) for the recommended pattern.

## Common patterns

### Accessing raw body (for webhooks)

```ts
resolve: async (c) => {
  const rawBody = await c.request.text();
  const signature = c.request.headers.get("x-signature");
  // Verify signature against rawBody
},
```

### Getting the URL path

```ts
resolve: (c) => {
  const url = new URL(c.request.url);
  const path = url.pathname;     // "/v2/books"
  const search = url.search;     // "?genre=fiction"
},
```
