Error Handling
How errors are formatted and returned in REST responses.
The handler uses tRPC's error system. All errors flow through your errorFormatter and are returned as JSON responses with appropriate HTTP status codes.
Error response format
{
"message": "Input validation failed",
"code": "BAD_REQUEST",
"issues": [
{ "message": "Expected string, received number" }
]
}The issues field is only present for input validation errors (when the Zod parse fails).
HTTP status codes
| tRPC Code | HTTP Status |
|---|---|
BAD_REQUEST | 400 |
UNAUTHORIZED | 401 |
FORBIDDEN | 403 |
NOT_FOUND | 404 |
TIMEOUT | 408 |
CONFLICT | 409 |
UNPROCESSABLE_CONTENT | 422 |
TOO_MANY_REQUESTS | 429 |
INTERNAL_SERVER_ERROR | 500 |
PARSE_ERROR | 400 |
UNSUPPORTED_MEDIA_TYPE | 415 |
Error formatter integration
Your tRPC errorFormatter runs on all errors:
const t = initTRPC.meta<OpenApiMeta>().create({
errorFormatter({ shape, error }) {
return {
...shape,
message: simplifyError(error),
code: error.code,
};
},
});Input validation errors
When Zod validation fails, tRPC throws a BAD_REQUEST error with the Zod error as the cause. The handler detects this and:
- Sets the message to
"Input validation failed" - Includes the Zod
issuesarray in the response
Non-TRPCError handling
If a procedure throws a plain Error (not a TRPCError), the handler wraps it as an INTERNAL_SERVER_ERROR. The original error message is hidden from the client. Use onError to log the real error:
createOpenApiFetchHandler({
// ...
onError: ({ error }) => {
if (error.code === 'INTERNAL_SERVER_ERROR') {
console.error('Unhandled error:', error.cause);
}
},
});TRPC_ERROR_CODE_HTTP_STATUS
The full error code to status code mapping is exported:
import { TRPC_ERROR_CODE_HTTP_STATUS } from 'trpc-rest';
const status = TRPC_ERROR_CODE_HTTP_STATUS['NOT_FOUND']; // 404