denoland/deno · error · TypeError
Return value from onError handler must be a response or a…
Error message
Return value from onError handler must be a response or a promise resolving to a response
What it means
When the main handler throws (including on the validation errors above), Deno.serve calls the configured onError and applies the same contract: it must return a Response. This TypeError fires when onError's settled return value is not on the Response prototype chain — typically because a logging-only onError implicitly returns undefined. The failure then falls back to a built-in 500 and the error is logged as 'Exception in onError while handling exception'.
Solutions
- End every onError path with `return new Response('Internal Server Error', { status: 500 })`
- If you only want logging, keep the default onError and do logging in the handler's catch instead
- For typed errors, map to statuses: `return new Response(e.message, { status: e.status ?? 500 })`
Example fix
// before
Deno.serve({
onError: (e) => { console.error('failed:', e); }, // returns undefined
handler,
});
// after
Deno.serve({
onError: (e) => {
console.error('failed:', e);
return new Response('Internal Server Error', { status: 500 });
},
handler,
}); Defensive patterns
Strategy: type-guard
Validate before calling
// onError must produce a Response on every path:
const onError = (e: unknown): Response => {
console.error(e);
return e instanceof HttpError
? new Response(e.message, { status: e.status })
: new Response('Internal Server Error', { status: 500 });
}; Type guard
const isResponse = (v: unknown): v is Response => v instanceof Response;
Try / catch
// If onError itself is third-party, wrap it so its result is always a Response:
Deno.serve({
handler,
onError: (e) => {
const out = foreignOnError(e);
return out instanceof Response ? out : new Response('Internal Server Error', { status: 500 });
},
}); Prevention
- Annotate onError with an explicit Response return type so TS flags missing returns
- Remember Deno.serve onError must RETURN the response, unlike Express error middleware
- If you only want logging, keep the default onError and log in the handler instead
When it happens
Trigger: `onError: (e) => { console.error(e); }` (returns undefined), an async onError that awaits logging but forgets to return, onError returning a plain object or string, or onError throwing and its replacement value being validated.
Common situations: Adding an onError purely for structured logging/metrics and forgetting it must also produce the response, teams assuming onError behaves like Express error middleware (which sends the response itself).
Related errors
- Return value from serve handler must be a response or a…
- Cannot serve HTTP requests: either a `handler` or `options`…
- Cannot serve HTTP requests: handler must be a function…
- Return value from onError handler must be a Response…
- Both 'cert' and 'key' must be provided to enable HTTPS
AI-assisted analysis of denoland/deno@abd22074e4 (2026-08-20).
Data as JSON: /api/errors/04bde810e2a261ae.
Report an issue: GitHub.
Appendix: source
Thrown at ext/http/00_serve.ts:774
);
}
if (inner.type === "error") {
throw new TypeError(
"Return value from serve handler must not be an error response (like Response.error())",
);
}
if (responseBodyUsed(response)) {
throw new TypeError(
"The body of the Response returned from the serve handler has already been consumed",
);
}
} catch (error) {
try {
response = await onError(error);
if (!ObjectPrototypeIsPrototypeOf(ResponsePrototype, response)) {
throw new TypeError(
"Return value from onError handler must be a response or a promise resolving to a response",
);
}
inner = toInnerResponse(response);
if (inner === undefined) {
throw new TypeError(
"Return value from onError handler must be a Response constructed via the Response constructor in this realm",
);
}
} catch (error) {
if (otelState.METRICS_ENABLED) {
op_http_metric_handle_otel_error(req);
}
internals.log(
"error",
"Exception in onError while handling exception",
error,
);View on GitHub (pinned to abd22074e4)