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.
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.
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.
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.
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.
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.
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.
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.
// 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:
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 for the recommended pattern.
Common patterns
Accessing raw body (for webhooks)
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
resolve: (c) => {
const url = new URL(c.request.url);
const path = url.pathname; // "/v2/books"
const search = url.search; // "?genre=fiction"
},