Custom error formatting example with Zod validation
Example showing how to add custom formatting to include Zod validation errors: create a tRPC instance with initTRPC.create() and define errorFormatter to check if error.code is 'BAD_REQUEST' and error.cause is a ZodError instance, then include error.cause.flatten() in the returned shape data.
Error formatting example code
```ts
import { initTRPC } from '@trpc/server';
import { ZodError } from 'zod';
export const t = initTRPC.create({
errorFormatter(opts) {
const { shape, error } = opts;
return {
...shape,
data: {
...shape.data,
zodError:
error.code === 'BAD_REQUEST' && error.cause instanceof ZodError
? error.cause.flatten()
: null,
},
};
},
});
```
Error formatting in routers is inferred to client
The error formatting defined in your tRPC router will be automatically inferred all the way to your client, enabling type-safe error handling.
Accessing custom error data in React client
When using tRPC React Query integration, custom error data added via errorFormatter is accessible and type-inferred on the client. For example, mutation.error?.data?.zodError will be inferred with proper typing when accessed in a React component using useMutation().
tRPC is JSON-RPC 2.0 compliant
tRPC follows the JSON-RPC 2.0 specification for error formatting.
DefaultErrorShape structure
The default error shape returned by tRPC contains: message (string), code (TRPC_ERROR_CODE_NUMBER), and data object. The data object contains code (TRPC_ERROR_CODE_KEY), httpStatus (number), path (optional string to the procedure that threw the error), and stack (optional string with stack trace, only in development).
errorFormatter option signature
The errorFormatter function receives an ErrorFormatterOpts object with properties: error (TRPCError), type ('query' | 'mutation' | 'subscription' | 'unknown'), path (string | undefined), input (unknown), ctx (unknown), and shape ({ message: string; code: number; data: unknown }).
onError handler example in standalone server
import { createHTTPServer } from '@trpc/server/adapters/standalone';
import { appRouter } from './router';
const server = createHTTPServer({
router: appRouter,
onError(opts) {
const { error, type, path, input, ctx, req } = opts;
console.error('Error:', error);
if (error.code === 'INTERNAL_SERVER_ERROR') {
// send to bug reporting
}
},
});
OnErrorOpts interface definition
interface OnErrorOpts {
error: TRPCError;
type: 'query' | 'mutation' | 'subscription' | 'unknown';
path: string | undefined;
input: unknown;
ctx: unknown;
req: Request;
}
Error response example with code mapping
An example error response for bad request input looks like: {"id": null, "error": {"message": "\"password\" must be at least 4 characters", "code": -32600, "data": {"code": "BAD_REQUEST", "httpStatus": 400, "stack": "...", "path": "user.changepassword"}}}
TRPCError throwing example
import { initTRPC, TRPCError } from '@trpc/server';
const t = initTRPC.create();
const theError = new Error('something went wrong');
const appRouter = t.router({
hello: t.procedure.query(() => {
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'An unexpected error occurred, please try again later.',
// optional: pass the original error to retain stack trace
cause: theError,
});
}),
});
Error response object structure
When an error occurs in a procedure, tRPC responds to the client with an object containing an 'error' property. The error object includes a message, a code (as a negative integer), and a data object. The data object contains the code name (like 'BAD_REQUEST'), the HTTP status, stack trace (if isDev is true), and the path where the error occurred.
Stack traces in production mode
By default, tRPC includes error.data.stack only when isDev is true. initTRPC.create() sets isDev to process.env.NODE_ENV !== 'production' by default. To override this behavior and control stack trace visibility, manually set isDev when creating tRPC with initTRPC.create({ isDev: false }).
tRPC error codes and HTTP status mapping
tRPC defines these error codes with their descriptions and HTTP status codes: PARSE_ERROR (400) - Invalid JSON or parsing error; BAD_REQUEST (400) - Client error in request; UNAUTHORIZED (401) - Missing authentication credentials; PAYMENT_REQUIRED (402) - Payment required for resource; FORBIDDEN (403) - Client not authorized to access resource; NOT_FOUND (404) - Server cannot find resource; METHOD_NOT_SUPPORTED (405) - Request method not supported for resource; TIMEOUT (408) - Server wants to shut down unused connection; CONFLICT (409) - Request conflicts with current resource state; PRECONDITION_FAILED (412) - Access to resource denied; PAYLOAD_TOO_LARGE (413) - Request entity larger than server limits; UNSUPPORTED_MEDIA_TYPE (415) - Payload format unsupported; UNPROCESSABLE_CONTENT (422) - Server unable to process correct request; PRECONDITION_REQUIRED (428) - Required precondition header missing; TOO_MANY_REQUESTS (429) - Rate limit exceeded or too many requests; CLIENT_CLOSED_REQUEST (499) - Client closed connection before server finished responding; INTERNAL_SERVER_ERROR (500) - Unspecified error; NOT_IMPLEMENTED (501) - Server does not support required functionality; BAD_GATEWAY (502) - Invalid response from upstream server; SERVICE_UNAVAILABLE (503) - Server not ready to handle request; GATEWAY_TIMEOUT (504) - No response from upstream server in time.
getHTTPStatusCodeFromError helper function
tRPC provides getHTTPStatusCodeFromError imported from '@trpc/server/http' to extract the HTTP status code from a TRPCError. The function takes a TRPCError object and returns its corresponding HTTP status code as an integer.
TRPCError class for throwing errors
tRPC provides a TRPCError subclass that you can throw from procedures to represent errors. When creating a TRPCError, pass an object with: code (required) - one of the defined error codes; message (required) - error message to send to client; cause (optional) - the original error to retain the stack trace.
onError handler in server configuration
All errors that occur in a procedure go through the onError method before being sent to the client. The onError method receives an object with properties: error (TRPCError), type ('query' | 'mutation' | 'subscription' | 'unknown'), path (string | undefined), input (unknown), ctx (unknown), and req (Request). This is where you can handle errors like sending them to bug reporting services.
Throwing TRPCError in middleware for access control
When building middleware or base procedures for access control, use throw new TRPCError() with appropriate error codes such as 'UNAUTHORIZED' for unauthenticated users or 'FORBIDDEN' for users without required permissions.
createCaller onError option
Both createCallerFactory and createCaller can take an onError option to handle errors. The handler receives the same arguments as an error formatter except for the shape field. Any handler passed to createCallerFactory will be called before the handler passed to createCaller.
OnErrorShape interface for createCaller
The error handler passed to createCaller or createCallerFactory receives an object with this shape:
```ts
interface OnErrorShape {
ctx: unknown;
error: TRPCError;
path: string | undefined;
input: unknown;
type: 'query' | 'mutation' | 'subscription' | 'unknown';
}
```
createCaller onError handler example
Example showing onError handler with createCaller:
```ts
const t = initTRPC.context<{ foo?: 'bar' }>().create();
const router = t.router({
greeting: t.procedure.input(z.object({ name: z.string() })).query((opts) => {
if (opts.input.name === 'invalid') {
throw new Error('Invalid name');
}
return `Hello ${opts.input.name}`;
}),
});
const caller = router.createCaller(
{},
{
onError: (opts) => {
console.error('An error occurred:', opts.error);
},
},
);
await caller.greeting({ name: 'invalid' });
```
Error handling in subscriptions
Throwing an error in a generator function propagates to tRPC's onError() on the backend. If the error thrown is a 5xx error, the client will automatically attempt to reconnect based on the last event id that is tracked using tracked(). For other errors, the subscription will be cancelled and propagate to the onError() callback.
TRPCError for throwing NOT_FOUND
Import TRPCError from '@trpc/server' and throw new TRPCError({ code: 'NOT_FOUND' }) when a resource cannot be found.
tRPC error handling and formatting
Throw typed errors from procedures, format errors for clients, and implement global error handling. Refer to error-handling skill for patterns.
Output validation failure response
If output validation fails, the server will respond with an INTERNAL_SERVER_ERROR.