TRPCError code mismatch when client and server share AppRouter type but handle NOT_FOUND differently
0 reputation · 27 Jun 2026, 11:27 UTC
0 reputation · 27 Jun 2026, 11:27 UTC
When a server procedure throws a TRPCError with code NOT_FOUND, the client receives an error object whose shape reflects the mapped HTTP status. However, the inferred type from the shared AppRouter does not appear to constrain the possible error codes per procedure, allowing the client to handle only a subset of codes at compile time while the server may throw additional codes at runtime.
AppRouter type definition.throw new TRPCError({ code: 'NOT_FOUND' }) in a procedure.try/catch around the typed procedure call.TRPCError code thrown by a procedure is represented in the client-side error union for that procedure?34775 reputation · 27 Jun 2026, 16:12 UTC
No – the shared AppRouter type does not automatically constrain the error union to the specific TRPCError codes a procedure may throw. Without an explicit error shape, the client infers the error as TRPCError<unknown>, so compile‑time checking cannot enforce handling of NOT_FOUND versus other codes.
errorShape or meta to a procedure to describe the possible error codes.errorShape is omitted, the generated client type for a procedure is TRPCError<unknown> (generic).default: never case.as const object) of the error codes you intend to use, e.g. enum AppErrorCode { NotFound = 'NOT_FOUND', Unauthorized = 'UNAUTHORIZED' }.throw new TRPCError({ code: AppErrorCode.NotFound }).errorShape (or meta({ possibleCodes: AppErrorCode }) as const) to the procedure so the client type becomes a union of specific TRPCError<AppErrorCode.NotFound> etc.typeof appRouter.procedures['myProc'] and catch the error; then switch on error.code with cases for each enum value and a default: never to achieve exhaustiveness.Create two test procedures, one throwing NOT_FOUND and another UNAUTHORIZED. In your IDE, hover over the call site; without errorShape you will see TRPCError<unknown>. After adding the shape, the hover shows a union like TRPCError<'NOT_FOUND'> | TRPCError<'UNAUTHORIZED'>. At runtime, a switch (error.code) with cases for each enum and a default: never will compile only if all codes are covered.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.