From 8f9cfa9e331abf520787c040b002754531e1d64c Mon Sep 17 00:00:00 2001 From: arsalanghogari Date: Sat, 1 Aug 2026 02:18:02 -0700 Subject: [PATCH 1/2] docs(zod-validator,standard-validator): document default error response format Documents the exact JSON body each validator returns on validation failure when no hook is provided, and adds a hook example producing an RFC 9457 Problem Details response. Refs honojs/middleware#2049 --- packages/standard-validator/README.md | 51 +++++++++++++++++++++++++++ packages/zod-validator/README.md | 43 ++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/packages/standard-validator/README.md b/packages/standard-validator/README.md index 8ea6f1925..51bab643b 100644 --- a/packages/standard-validator/README.md +++ b/packages/standard-validator/README.md @@ -41,6 +41,57 @@ app.post( ) ``` +### Default error response + +If validation fails and no hook is provided, the middleware responds with `400 Bad Request`. The body contains the original input (`data`) and the schema library's issues (`error`). For the schema above, sending `{ "name": "a", "age": "x" }` returns: + +```json +{ + "data": { + "name": "a", + "age": "x" + }, + "error": [ + { + "expected": "number", + "code": "invalid_type", + "path": ["age"], + "message": "Invalid input: expected number, received string" + } + ], + "success": false +} +``` + +The issue objects in `error` come from the underlying schema library (Zod in this example), so their fields vary by library and are not guaranteed to be stable. Also note that `data` echoes the raw request input back to the client. If that is undesirable (for example, the request may contain sensitive values) or clients parse the error body, define your own format with a hook. For example, an [RFC 9457 Problem Details](https://datatracker.ietf.org/doc/html/rfc9457) body: + +```ts +app.post( + '/post', + sValidator('json', schema, (result, c) => { + if (!result.success) { + return c.json( + { + type: 'about:blank', + title: 'Bad Request', + status: 400, + detail: 'Request validation failed.', + errors: result.error.map((issue) => ({ + detail: issue.message, + pointer: `#/${(issue.path ?? []) + .map((p) => (typeof p === 'object' ? String(p.key) : String(p))) + .join('/')}`, + })), + }, + 400, + { 'content-type': 'application/problem+json' } + ) + } + }) + //... +) +``` + ### Headers: Headers are internally transformed to lower-case in Hono. Hence, you will have to make them lower-cased in validation object. diff --git a/packages/zod-validator/README.md b/packages/zod-validator/README.md index 46b503bba..9750bbbda 100644 --- a/packages/zod-validator/README.md +++ b/packages/zod-validator/README.md @@ -24,6 +24,22 @@ app.post('/author', zValidator('json', schema), (c) => { }) ``` +### Default error response + +If validation fails and no hook is provided, the middleware responds with `400 Bad Request` and the serialized Zod `safeParse` result as the body. For the schema above, sending `{ "name": "a", "age": "x" }` returns: + +```json +{ + "success": false, + "error": { + "name": "ZodError", + "message": "[\n {\n \"expected\": \"number\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"age\"\n ],\n \"message\": \"Invalid input: expected number, received string\"\n }\n]" + } +} +``` + +Note that `error.message` is a JSON string of Zod's issue array, not a nested object. This format is Zod's own serialization and is not guaranteed to be stable across Zod versions. If clients parse the error body, define your own format with a hook (see below). + Hook: ```ts @@ -38,6 +54,33 @@ app.post( ) ``` +For example, a hook returning an [RFC 9457 Problem Details](https://datatracker.ietf.org/doc/html/rfc9457) body: + +```ts +app.post( + '/post', + zValidator('json', schema, (result, c) => { + if (!result.success) { + return c.json( + { + type: 'about:blank', + title: 'Bad Request', + status: 400, + detail: 'Request validation failed.', + errors: result.error.issues.map((issue) => ({ + detail: issue.message, + pointer: `#/${issue.path.join('/')}`, + })), + }, + 400, + { 'content-type': 'application/problem+json' } + ) + } + }) + //... +) +``` + Throw Error: throw a zod validate error instead of directly returning an error response. From 6ae4b1c49339c65e1ad9d7b461a2019b940d1e5f Mon Sep 17 00:00:00 2001 From: arsalanghogari Date: Sat, 1 Aug 2026 02:46:12 -0700 Subject: [PATCH 2/2] docs(zod-validator): note Zod v3 vs v4 default error body difference --- packages/zod-validator/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/zod-validator/README.md b/packages/zod-validator/README.md index 9750bbbda..223e59647 100644 --- a/packages/zod-validator/README.md +++ b/packages/zod-validator/README.md @@ -38,7 +38,7 @@ If validation fails and no hook is provided, the middleware responds with `400 B } ``` -Note that `error.message` is a JSON string of Zod's issue array, not a nested object. This format is Zod's own serialization and is not guaranteed to be stable across Zod versions. If clients parse the error body, define your own format with a hook (see below). +Note that `error.message` is a JSON string of Zod's issue array, not a nested object. This is the Zod v4 shape; with Zod v3 the same code serializes the issues inline instead (`"error": { "issues": [...], "name": "ZodError" }`). The format is Zod's own serialization and is not guaranteed to be stable across Zod versions. If clients parse the error body, define your own format with a hook (see below). Hook: