Skip to content
tRPC

Error Handling

On this page

A failed procedure call rejects with a TRPCClientError. You can either let it throw and catch it, or use safe() to get it back as a type-safe value.

Catching a thrown error

query() and mutate() behave like any other promise, so a try/catch works as you would expect. TypeScript has no way to describe what a function throws, though, so cause arrives as unknown and has to be narrowed:

client.ts
import { class TRPCClientError<TRouterOrProcedure extends InferrableClientTypes>TRPCClientError } from '@trpc/client';
import type { 
type AppRouter = Router<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        byId: QueryProcedure<{
            input: string;
            output: {
                id: number;
                title: string;
            };
            meta: object;
            errorShape: FoldErrorShape<DefaultErrorShape, {
                data: {
                    kind: "RATE_LIMIT";
                    retryAfterMs: number;
                    code: TRPC_ERROR_CODE_KEY;
                    httpStatus: number;
                    path?: string;
                    stack?: string;
                };
                message: string;
                code: TRPC_ERROR_CODE_NUMBER;
            } | undefined>;
        }>;
    }>>;
}>> & DecorateCreateRouterOptions<...>
AppRouter
} from './server';
import {
const trpc: TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        byId: QueryProcedure<{
            input: string;
            output: {
                id: number;
                title: string;
            };
            meta: object;
            errorShape: FoldErrorShape<DefaultErrorShape, {
                data: {
                    kind: "RATE_LIMIT";
                    retryAfterMs: number;
                    code: TRPC_ERROR_CODE_KEY;
                    httpStatus: number;
                    path?: string;
                    stack?: string;
                };
                message: string;
                code: TRPC_ERROR_CODE_NUMBER;
            } | undefined>;
        }>;
    }>>;
}>>>
trpc
} from './trpc';
try { const
const post: {
    id: number;
    title: string;
}
post
= await
const trpc: TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        byId: QueryProcedure<{
            input: string;
            output: {
                id: number;
                title: string;
            };
            meta: object;
            errorShape: FoldErrorShape<DefaultErrorShape, {
                data: {
                    kind: "RATE_LIMIT";
                    retryAfterMs: number;
                    code: TRPC_ERROR_CODE_KEY;
                    httpStatus: number;
                    path?: string;
                    stack?: string;
                };
                message: string;
                code: TRPC_ERROR_CODE_NUMBER;
            } | undefined>;
        }>;
    }>>;
}>>>
trpc
.
post: DecoratedProcedureRecord<{
    transformer: false;
    errorShape: DefaultErrorShape;
}, DecorateCreateRouterOptions<{
    byId: QueryProcedure<{
        input: string;
        output: {
            id: number;
            title: string;
        };
        meta: object;
        errorShape: FoldErrorShape<DefaultErrorShape, {
            data: {
                kind: "RATE_LIMIT";
                retryAfterMs: number;
                code: TRPC_ERROR_CODE_KEY;
                httpStatus: number;
                path?: string;
                stack?: string;
            };
            message: string;
            code: TRPC_ERROR_CODE_NUMBER;
        } | undefined>;
    }>;
}>>
post
.
byId: {
    query: Resolver<{
        input: string;
        output: {
            id: number;
            title: string;
        };
        errorShape: FoldErrorShape<DefaultErrorShape, {
            data: {
                kind: "RATE_LIMIT";
                retryAfterMs: number;
                code: TRPC_ERROR_CODE_KEY;
                httpStatus: number;
                path?: string;
                stack?: string;
            };
            message: string;
            code: TRPC_ERROR_CODE_NUMBER;
        } | undefined>;
        transformer: false;
    }>;
}
byId
.
query: (input: string, opts?: TRPCProcedureOptions) => TRPCTaggedPromise<{
    id: number;
    title: string;
}, FoldErrorShape<DefaultErrorShape, {
    data: {
        kind: "RATE_LIMIT";
        retryAfterMs: number;
        code: TRPC_ERROR_CODE_KEY;
        httpStatus: number;
        path?: string;
        stack?: string;
    };
    message: string;
    code: TRPC_ERROR_CODE_NUMBER;
} | undefined>>
query
('1');
} catch (var cause: unknowncause) { if (var cause: unknowncause instanceof class TRPCClientError<TRouterOrProcedure extends InferrableClientTypes>TRPCClientError) { var console: Consoleconsole.Console.log(...data: any[]): voidlog(var cause: TRPCClientError<any>cause.TRPCClientError<any>.data: anydata); } else { // something that isn't a tRPC error } }

See Inferring Types for a reusable isTRPCClientError guard.

Errors as values with safe()

safe() awaits a query() or mutate() call without letting it throw, handing back a [data, error] pair instead. Exactly one side is ever set, so checking either one narrows the other:

client.ts
import { function safe<TData, TErrorShape>(promise: TRPCTaggedPromise<TData, TErrorShape>): Promise<SafeResult<TData, TErrorShape>>safe } from '@trpc/client';
import { 
const trpc: TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        byId: QueryProcedure<{
            input: string;
            output: {
                id: number;
                title: string;
            };
            meta: object;
            errorShape: FoldErrorShape<DefaultErrorShape, {
                data: {
                    kind: "RATE_LIMIT";
                    retryAfterMs: number;
                    code: TRPC_ERROR_CODE_KEY;
                    httpStatus: number;
                    path?: string;
                    stack?: string;
                };
                message: string;
                code: TRPC_ERROR_CODE_NUMBER;
            } | undefined>;
        }>;
    }>>;
}>>>
trpc
} from './trpc';
const [
const post: {
    id: number;
    title: string;
} | undefined
post
,
const error: inferSafeError<FoldErrorShape<DefaultErrorShape, {
    data: {
        kind: "RATE_LIMIT";
        retryAfterMs: number;
        code: TRPC_ERROR_CODE_KEY;
        httpStatus: number;
        path?: string;
        stack?: string;
    };
    message: string;
    code: TRPC_ERROR_CODE_NUMBER;
} | undefined>> | undefined
error
] = await
safe<{
    id: number;
    title: string;
}, FoldErrorShape<DefaultErrorShape, {
    data: {
        kind: "RATE_LIMIT";
        retryAfterMs: number;
        code: TRPC_ERROR_CODE_KEY;
        httpStatus: number;
        path?: string;
        stack?: string;
    };
    message: string;
    code: TRPC_ERROR_CODE_NUMBER;
} | undefined>>(promise: TRPCTaggedPromise<{
    id: number;
    title: string;
}, FoldErrorShape<DefaultErrorShape, {
    data: {
        kind: "RATE_LIMIT";
        retryAfterMs: number;
        code: TRPC_ERROR_CODE_KEY;
        httpStatus: number;
        path?: string;
        stack?: string;
    };
    message: string;
    code: TRPC_ERROR_CODE_NUMBER;
} | undefined>>): Promise<...>
safe
(
const trpc: TRPCClient<BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    post: BuiltRouter<{
        ctx: object;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        byId: QueryProcedure<{
            input: string;
            output: {
                id: number;
                title: string;
            };
            meta: object;
            errorShape: FoldErrorShape<DefaultErrorShape, {
                data: {
                    kind: "RATE_LIMIT";
                    retryAfterMs: number;
                    code: TRPC_ERROR_CODE_KEY;
                    httpStatus: number;
                    path?: string;
                    stack?: string;
                };
                message: string;
                code: TRPC_ERROR_CODE_NUMBER;
            } | undefined>;
        }>;
    }>>;
}>>>
trpc
.
post: DecoratedProcedureRecord<{
    transformer: false;
    errorShape: DefaultErrorShape;
}, DecorateCreateRouterOptions<{
    byId: QueryProcedure<{
        input: string;
        output: {
            id: number;
            title: string;
        };
        meta: object;
        errorShape: FoldErrorShape<DefaultErrorShape, {
            data: {
                kind: "RATE_LIMIT";
                retryAfterMs: number;
                code: TRPC_ERROR_CODE_KEY;
                httpStatus: number;
                path?: string;
                stack?: string;
            };
            message: string;
            code: TRPC_ERROR_CODE_NUMBER;
        } | undefined>;
    }>;
}>>
post
.
byId: {
    query: Resolver<{
        input: string;
        output: {
            id: number;
            title: string;
        };
        errorShape: FoldErrorShape<DefaultErrorShape, {
            data: {
                kind: "RATE_LIMIT";
                retryAfterMs: number;
                code: TRPC_ERROR_CODE_KEY;
                httpStatus: number;
                path?: string;
                stack?: string;
            };
            message: string;
            code: TRPC_ERROR_CODE_NUMBER;
        } | undefined>;
        transformer: false;
    }>;
}
byId
.
query: (input: string, opts?: TRPCProcedureOptions) => TRPCTaggedPromise<{
    id: number;
    title: string;
}, FoldErrorShape<DefaultErrorShape, {
    data: {
        kind: "RATE_LIMIT";
        retryAfterMs: number;
        code: TRPC_ERROR_CODE_KEY;
        httpStatus: number;
        path?: string;
        stack?: string;
    };
    message: string;
    code: TRPC_ERROR_CODE_NUMBER;
} | undefined>>
query
('1'));
if (
const error: inferSafeError<FoldErrorShape<DefaultErrorShape, {
    data: {
        kind: "RATE_LIMIT";
        retryAfterMs: number;
        code: TRPC_ERROR_CODE_KEY;
        httpStatus: number;
        path?: string;
        stack?: string;
    };
    message: string;
    code: TRPC_ERROR_CODE_NUMBER;
} | undefined>> | undefined
error
) {
var console: Consoleconsole.Console.log(...data: any[]): voidlog(
const error: inferSafeError<FoldErrorShape<DefaultErrorShape, {
    data: {
        kind: "RATE_LIMIT";
        retryAfterMs: number;
        code: TRPC_ERROR_CODE_KEY;
        httpStatus: number;
        path?: string;
        stack?: string;
    };
    message: string;
    code: TRPC_ERROR_CODE_NUMBER;
} | undefined>>
error
.data);
TRPCClientError<{ errorShape: FoldErrorShape<DefaultErrorShape, { data: { kind: "RATE_LIMIT"; retryAfterMs: number; code: TRPC_ERROR_CODE_KEY; httpStatus: number; path?: string; stack?: string; }; message: string; code: TRPC_ERROR_CODE_NUMBER; } | undefined>; transformer: boolean; }>.data: Maybe<DefaultErrorData | {
    kind: "RATE_LIMIT";
    retryAfterMs: number;
    code: TRPC_ERROR_CODE_KEY;
    httpStatus: number;
    path?: string;
    stack?: string;
}>
} else { var console: Consoleconsole.Console.log(...data: any[]): voidlog(
const post: {
    id: number;
    title: string;
}
post
.title: stringtitle);
}