[zod-openapi] Absent optional body: validate it as {} (or type it as optional) instead of passing an unvalidated {} · Issue #2188 · honojs/middleware · GitHub
Skip to content

[zod-openapi] Absent optional body: validate it as {} (or type it as optional) instead of passing an unvalidated {} #2188

Open

Description

@cqr

Which middleware is the feature for?

@hono/zod-openapi

What is the feature you are proposing?

The README says that with request.body.required unset, "a request with no body reaches the handler and c.req.valid('json') is {}". That {} is injected without running the schema, but it's typed as the schema's output, so an absent body and an explicit {} behave differently:

const route = createRoute({
  method: "post", path: "/x",
  request: { body: { content: { "application/json": {
    schema: z.object({ limit: z.number().default(20) }),
  } } } },
  responses: { 200: { description: "ok" } },
});
app.openapi(route, (c) => c.json(c.req.valid("json")));

await app.request("/x", { method: "POST" });                       // 200 {}          (no default)
await app.request("/x", { method: "POST",
  headers: { "content-type": "application/json" }, body: "{}" });  // 200 {"limit":20}

With z.object({ name: z.string() }), an absent body reaches the handler as {} typed { name: string }, while an explicit {} is a 400.

Proposal: when an optional body is absent, either run the schema on {}, so defaults apply and errors are reported, or pass undefined and type c.req.valid('json') as T | undefined. Either way, the type matches what the handler actually gets.

Workaround we use: route middleware that, when there's no body and no Content-Type, rewrites the request to content-type: application/json with body {}, so the validator runs.

Related: #891 and #2087 (the 415 for an undeclared content type).

Activity

  1. cqr commented on Oct 19, 2026

    @cqr
    Author

    FYI I am happy to build this, but I don't want to pile on and it would help to know which approach the maintainers would prefer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions