Errors
One error class, typed on the client from what resolvers return, with or without declarations.
On this page
There is one error class, TrpcError. Return error({ code }) from a resolver or a middleware, or fail with it in Effect code (Effect.fail(error(…)), yield* error(…)). The procedure's error union is inferred from those, with no declaration:
import { function error<const TCode extends string, TData = undefined>(opts: Omit<TrpcErrorOptions<TCode, TData>, "defined">): TrpcError<TCode, TData>error } from '@trpc12/trpc/server';
import { import zz } from 'zod';
const const byId: BuildProcedure<InputDef<RootDef<Context, {}>, z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>, "query", {
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
services: never;
unmapped: never;
}>
byId = const t: TrpcRoot<Context, {}, RootDef<Context, {}>>t.TrpcRoot<Context, {}, RootDef<Context, {}>>.procedure: ProcedureBuilder<RootDef<Context, {}>>procedure
.ProcedureBuilder<RootDef<Context, {}>>.input<z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>(schema: z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>): ProcedureBuilder<InputDef<RootDef<Context, {}>, z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>> (+1 overload)
input(import zz.function object<{
id: z.ZodString;
}>(shape?: {
id: z.ZodString;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>
object({ id: z.ZodStringid: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() }))
.ProcedureBuilder<InputDef<RootDef<Context, {}>, ZodObject<{ id: ZodString; }, $strip>>>.query<Post | TrpcError<"NOT_FOUND", {
id: string;
}>>(resolver: (opts: ResolverOpts<InputDef<RootDef<Context, {}>, z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>>) => Post | TrpcError<"NOT_FOUND", {
id: string;
}>): BuildProcedure<InputDef<RootDef<Context, {}>, z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>, "query", {
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
services: never;
unmapped: never;
}>
query(
(opts: ResolverOpts<InputDef<RootDef<Context, {}>, z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>>
opts) =>
const db: {
post: {
find: (id: string) => Post | undefined;
list: () => Post[];
create: (input: {
title: string;
author?: string | null;
}) => Post;
};
}
db.post: {
find: (id: string) => Post | undefined;
list: () => Post[];
create: (input: {
title: string;
author?: string | null;
}) => Post;
}
post.find: (id: string) => Post | undefinedfind(opts: ResolverOpts<InputDef<RootDef<Context, {}>, z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>>
opts.ResolverOpts<InputDef<RootDef<Context, {}>, ZodObject<{ id: ZodString; }, $strip>>>.input: {
id: string;
}
input.id: stringid) ??
error<"NOT_FOUND", {
id: string;
}>(opts: Omit<TrpcErrorOptions<"NOT_FOUND", {
id: string;
}>, "defined">): TrpcError<"NOT_FOUND", {
id: string;
}>
error({ code: "NOT_FOUND"code: 'NOT_FOUND', data?: {
id: string;
} | undefined
data: { id: stringid: opts: ResolverOpts<InputDef<RootDef<Context, {}>, z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>>
opts.ResolverOpts<InputDef<RootDef<Context, {}>, ZodObject<{ id: ZodString; }, $strip>>>.input: {
id: string;
}
input.id: stringid } }),
);Declaring errors
Declaring errors with .errors() is optional. It adds typed constructors, data schemas and entries in generateOpenApi's document. It also types what inference can't see: errors thrown from helpers (a throw has no type) and contracts, which have no resolver. experimental_generateOpenApiFromTypes documents inferred errors without declarations (The document).
import { import zz } from 'zod';
const const byId: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
declared: {
NOT_FOUND: TrpcError<"NOT_FOUND", {
id: string;
}>;
GONE: TrpcError<"GONE", undefined>;
};
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | TrpcError<"GONE", undefined>;
}>, z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>, "query", {
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
services: never;
unmapped: never;
}>
byId = const t: TrpcRoot<Context, {}, RootDef<Context, {}>>t.TrpcRoot<Context, {}, RootDef<Context, {}>>.procedure: ProcedureBuilder<RootDef<Context, {}>>procedure
.ProcedureBuilder<RootDef<Context, {}>>.errors<{
readonly NOT_FOUND: {
readonly data: z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>;
};
readonly GONE: {
readonly status: 410;
readonly message: "This post was removed";
};
}>(map: {
readonly NOT_FOUND: {
readonly data: z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>;
};
readonly GONE: {
readonly status: 410;
readonly message: "This post was removed";
};
}): ProcedureBuilder<With<RootDef<Context, {}>, {
declared: {
NOT_FOUND: TrpcError<"NOT_FOUND", {
id: string;
}>;
GONE: TrpcError<"GONE", undefined>;
};
errors: TrpcError<...> | TrpcError<...>;
}>>
errors({
type NOT_FOUND: {
readonly data: z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>;
}
NOT_FOUND: { data: z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>
data: import zz.function object<{
id: z.ZodString;
}>(shape?: {
id: z.ZodString;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>
object({ id: z.ZodStringid: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() }) },
type GONE: {
readonly status: 410;
readonly message: "This post was removed";
}
GONE: { status: 410status: 410, message: "This post was removed"message: 'This post was removed' },
})
.ProcedureBuilder<With<RootDef<Context, {}>, { declared: { NOT_FOUND: TrpcError<"NOT_FOUND", { id: string; }>; GONE: TrpcError<"GONE", undefined>; }; errors: TrpcError<...> | TrpcError<...>; }>>.input<z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>(schema: z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>): ProcedureBuilder<InputDef<With<RootDef<Context, {}>, {
declared: {
NOT_FOUND: TrpcError<"NOT_FOUND", {
id: string;
}>;
GONE: TrpcError<"GONE", undefined>;
};
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | TrpcError<"GONE", undefined>;
}>, z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>> (+1 overload)
input(import zz.function object<{
id: z.ZodString;
}>(shape?: {
id: z.ZodString;
} | undefined, params?: string | {
error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueInvalidType<unknown> | z.core.$ZodIssueUnrecognizedKeys>> | undefined;
message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>
object({ id: z.ZodStringid: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() }))
.ProcedureBuilder<InputDef<With<RootDef<Context, {}>, { declared: { NOT_FOUND: TrpcError<"NOT_FOUND", { id: string; }>; GONE: TrpcError<"GONE", undefined>; }; errors: TrpcError<...> | TrpcError<...>; }>, ZodObject<...>>>.query<Post | TrpcError<"NOT_FOUND", {
id: string;
}>>(resolver: (opts: ResolverOpts<InputDef<With<RootDef<Context, {}>, {
declared: {
NOT_FOUND: TrpcError<"NOT_FOUND", {
id: string;
}>;
GONE: TrpcError<"GONE", undefined>;
};
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | TrpcError<"GONE", undefined>;
}>, z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>>) => Post | TrpcError<"NOT_FOUND", {
id: string;
}>): BuildProcedure<...>
query(
(opts: ResolverOpts<InputDef<With<RootDef<Context, {}>, {
declared: {
NOT_FOUND: TrpcError<"NOT_FOUND", {
id: string;
}>;
GONE: TrpcError<"GONE", undefined>;
};
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | TrpcError<"GONE", undefined>;
}>, z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>>
opts) =>
const db: {
post: {
find: (id: string) => Post | undefined;
list: () => Post[];
create: (input: {
title: string;
author?: string | null;
}) => Post;
};
}
db.post: {
find: (id: string) => Post | undefined;
list: () => Post[];
create: (input: {
title: string;
author?: string | null;
}) => Post;
}
post.find: (id: string) => Post | undefinedfind(opts: ResolverOpts<InputDef<With<RootDef<Context, {}>, {
declared: {
NOT_FOUND: TrpcError<"NOT_FOUND", {
id: string;
}>;
GONE: TrpcError<"GONE", undefined>;
};
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | TrpcError<"GONE", undefined>;
}>, z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>>
opts.ResolverOpts<InputDef<With<RootDef<Context, {}>, { declared: { NOT_FOUND: TrpcError<"NOT_FOUND", { id: string; }>; GONE: TrpcError<"GONE", undefined>; }; errors: TrpcError<...> | TrpcError<...>; }>, ZodObject<...>>>.input: {
id: string;
}
input.id: stringid) ??
opts: ResolverOpts<InputDef<With<RootDef<Context, {}>, {
declared: {
NOT_FOUND: TrpcError<"NOT_FOUND", {
id: string;
}>;
GONE: TrpcError<"GONE", undefined>;
};
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | TrpcError<"GONE", undefined>;
}>, z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>>
opts.ResolverOpts<InputDef<With<RootDef<Context, {}>, { declared: { NOT_FOUND: TrpcError<"NOT_FOUND", { id: string; }>; GONE: TrpcError<"GONE", undefined>; }; errors: TrpcError<...> | TrpcError<...>; }>, ZodObject<...>>>.errors: ErrorConstructors<{
NOT_FOUND: TrpcError<"NOT_FOUND", {
id: string;
}>;
GONE: TrpcError<"GONE", undefined>;
}>
errors.type NOT_FOUND: (opts: ErrorConstructorOpts<{
id: string;
}>) => TrpcError<"NOT_FOUND", {
id: string;
}>
NOT_FOUND({ data: {
id: string;
}
data: { id: stringid: opts: ResolverOpts<InputDef<With<RootDef<Context, {}>, {
declared: {
NOT_FOUND: TrpcError<"NOT_FOUND", {
id: string;
}>;
GONE: TrpcError<"GONE", undefined>;
};
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | TrpcError<"GONE", undefined>;
}>, z.ZodObject<{
id: z.ZodString;
}, z.core.$strip>>>
opts.ResolverOpts<InputDef<With<RootDef<Context, {}>, { declared: { NOT_FOUND: TrpcError<"NOT_FOUND", { id: string; }>; GONE: TrpcError<"GONE", undefined>; }; errors: TrpcError<...> | TrpcError<...>; }>, ZodObject<...>>>.input: {
id: string;
}
input.id: stringid } }),
);On the client
On the client, error.defined is true for errors in the union, whether returned, failed or declared (narrow on code next). It is false for anything else: undeclared thrown errors, network failures, aborts. Unexpected errors' messages are masked unless NODE_ENV is development or test, or exposeUnexpectedErrors: true. There is no errorFormatter.
import { function safe<T, E = unknown>(promise: PromiseLike<T> & {
readonly "~error"?: E;
}): Promise<SafeResult<T, E>>
safe } from '@trpc12/trpc/client';
const [const post: {
id: string;
title: string;
author: string | null;
createdAt: string;
} | undefined
post, const err: UnexpectedError | (TrpcError<"NOT_FOUND", {
id: string;
}> & {
readonly defined: true;
}) | (TrpcError<"BAD_REQUEST", InputValidationErrorData<{
readonly code: "invalid_type";
readonly expected: $ZodInvalidTypeExpected;
readonly path: readonly [] | readonly ["id"];
readonly message: string;
} | {
readonly code: "too_big";
readonly origin: "number" | "int" | "bigint" | "date" | "string" | "array" | "set" | "file" | (string & {});
readonly maximum: string | number;
readonly inclusive?: boolean | undefined;
readonly exact?: boolean | undefined;
readonly path: readonly [] | readonly [...];
readonly message: string;
} | ... 9 more ... | {
...;
}>> & {
...;
}) | undefined
err] = await safe<{
id: string;
title: string;
author: string | null;
createdAt: string;
}, UnexpectedError | (TrpcError<"NOT_FOUND", {
id: string;
}> & {
readonly defined: true;
}) | (TrpcError<"BAD_REQUEST", InputValidationErrorData<{
readonly code: "invalid_type";
readonly expected: $ZodInvalidTypeExpected;
readonly path: readonly [] | readonly ["id"];
readonly message: string;
} | {
readonly code: "too_big";
readonly origin: "number" | "int" | "bigint" | "date" | "string" | "array" | "set" | "file" | (string & {});
... 4 more ...;
readonly message: string;
} | ... 9 more ... | {
...;
}>> & {
...;
})>(promise: PromiseLike<...> & {
...;
}): Promise<...>
safe(const client: TrpcClient<{
post: {
byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
id: ZodString;
}, $strip>>, "query", {
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
services: never;
unmapped: never;
}>;
list: BuildProcedure<RootDef<Context, {}>, "query", {
output: Post[];
errors: never;
services: never;
unmapped: never;
}>;
create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
ctx: Context;
ctxAdded: {
...;
};
inputOut: Unset;
errors: TrpcError<...>;
shorts: never;
}>, ZodObject<...>>, "mutation", {
...;
}>;
};
}, {}, undefined, LinksTypes<...>>
client.post: ClientRouter<{
byId: BuildProcedure<InputDef<RootDef<Context, {}>, ZodObject<{
id: ZodString;
}, $strip>>, "query", {
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}>;
services: never;
unmapped: never;
}>;
list: BuildProcedure<RootDef<Context, {}>, "query", {
output: Post[];
errors: never;
services: never;
unmapped: never;
}>;
create: BuildProcedure<InputDef<With<RootDef<Context, {}>, {
ctx: Context;
ctxAdded: {
...;
};
inputOut: Unset;
errors: TrpcError<...>;
shorts: never;
}>, ZodObject<...>>, "mutation", {
...;
}>;
}, {}, undefined, LinksTypes<...>>
post.byId: {
query(input: {
id: string;
}, opts?: CallOptions<{}> | undefined): TrpcPromise<{
id: string;
title: string;
author: string | null;
createdAt: string;
}, CallError<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | InputValidationError<{
readonly code: "invalid_type";
readonly expected: $ZodInvalidTypeExpected;
readonly path: readonly [] | readonly ["id"];
readonly message: string;
} | {
readonly code: "too_big";
readonly origin: "number" | "int" | "bigint" | "date" | "string" | "array" | "set" | "file" | (string & {});
... 4 more ...;
readonly message: string;
} | ... 9 more ... | {
...;
}>;
services: never;
}, JsonForm, never>>;
}
byId.function query(input: {
id: string;
}, opts?: CallOptions<{}> | undefined): TrpcPromise<{
id: string;
title: string;
author: string | null;
createdAt: string;
}, CallError<{
type: "query";
ctx: Context;
meta: {};
input: {
id: string;
};
output: Post;
errors: TrpcError<"NOT_FOUND", {
id: string;
}> | InputValidationError<{
readonly code: "invalid_type";
readonly expected: $ZodInvalidTypeExpected;
readonly path: readonly [] | readonly ["id"];
readonly message: string;
} | {
readonly code: "too_big";
readonly origin: "number" | "int" | "bigint" | "date" | "string" | "array" | "set" | "file" | (string & {});
... 4 more ...;
readonly message: string;
} | ... 9 more ... | {
...;
}>;
services: never;
}, JsonForm, never>>
query({ id: stringid: '1' }));
if (const err: UnexpectedError | (TrpcError<"NOT_FOUND", {
id: string;
}> & {
readonly defined: true;
}) | (TrpcError<"BAD_REQUEST", InputValidationErrorData<{
readonly code: "invalid_type";
readonly expected: $ZodInvalidTypeExpected;
readonly path: readonly [] | readonly ["id"];
readonly message: string;
} | {
readonly code: "too_big";
readonly origin: "number" | "int" | "bigint" | "date" | "string" | "array" | "set" | "file" | (string & {});
readonly maximum: string | number;
readonly inclusive?: boolean | undefined;
readonly exact?: boolean | undefined;
readonly path: readonly [] | readonly [...];
readonly message: string;
} | ... 9 more ... | {
...;
}>> & {
...;
}) | undefined
err?.defined: boolean | undefineddefined && const err: (TrpcError<"NOT_FOUND", {
id: string;
}> & {
readonly defined: true;
}) | (TrpcError<"BAD_REQUEST", InputValidationErrorData<{
readonly code: "invalid_type";
readonly expected: $ZodInvalidTypeExpected;
readonly path: readonly [] | readonly ["id"];
readonly message: string;
} | {
readonly code: "too_big";
readonly origin: "number" | "int" | "bigint" | "date" | "string" | "array" | "set" | "file" | (string & {});
readonly maximum: string | number;
readonly inclusive?: boolean | undefined;
readonly exact?: boolean | undefined;
readonly path: readonly [] | readonly [...];
readonly message: string;
} | ... 9 more ... | {
...;
}>> & {
...;
})
err.TrpcError<TCode extends string = string, TData = unknown>.code: "NOT_FOUND" | "BAD_REQUEST"code === 'NOT_FOUND') {
var console: Consoleconsole.Console.log(...data: any[]): voidlog(const err: TrpcError<"NOT_FOUND", {
id: string;
}> & {
readonly defined: true;
}
err.data);}See Errors without try/catch for safe() and createSafeClient().
Codes and statuses
Any string is a code. The built-in ones (NOT_FOUND, UNAUTHORIZED, …) have their HTTP status; a custom code is a 400 unless its declaration (.errors() or a TrpcError.Tagged class) has a status. The status is for HTTP: errors don't carry it, and neither do error bodies or socket frames.
const t: TrpcRoot<Context, {}, RootDef<Context, {}>>t.TrpcRoot<Context, {}, RootDef<Context, {}>>.procedure: ProcedureBuilder<RootDef<Context, {}>>procedure
.ProcedureBuilder<RootDef<Context, {}>>.errors<{
readonly OUT_OF_STOCK: {
readonly status: 409;
};
}>(map: {
readonly OUT_OF_STOCK: {
readonly status: 409;
};
}): ProcedureBuilder<With<RootDef<Context, {}>, {
declared: {
OUT_OF_STOCK: TrpcError<"OUT_OF_STOCK", undefined>;
};
errors: TrpcError<"OUT_OF_STOCK", undefined>;
}>>
errors({ type OUT_OF_STOCK: {
readonly status: 409;
}
OUT_OF_STOCK: { status: 409status: 409 } })
.ProcedureBuilder<With<RootDef<Context, {}>, { declared: { OUT_OF_STOCK: TrpcError<"OUT_OF_STOCK", undefined>; }; errors: TrpcError<"OUT_OF_STOCK", undefined>; }>>.mutation<TrpcError<"OUT_OF_STOCK", undefined>>(resolver: (opts: ResolverOpts<With<RootDef<Context, {}>, {
declared: {
OUT_OF_STOCK: TrpcError<"OUT_OF_STOCK", undefined>;
};
errors: TrpcError<"OUT_OF_STOCK", undefined>;
}>>) => TrpcError<"OUT_OF_STOCK", undefined>): BuildProcedure<With<RootDef<Context, {}>, {
declared: {
OUT_OF_STOCK: TrpcError<"OUT_OF_STOCK", undefined>;
};
errors: TrpcError<"OUT_OF_STOCK", undefined>;
}>, "mutation", {
...;
}>
mutation(() => error<"OUT_OF_STOCK", undefined>(opts: Omit<TrpcErrorOptions<"OUT_OF_STOCK", undefined>, "defined">): TrpcError<"OUT_OF_STOCK", undefined>error({ code: "OUT_OF_STOCK"code: 'OUT_OF_STOCK' })); // 409
const t: TrpcRoot<Context, {}, RootDef<Context, {}>>t.TrpcRoot<Context, {}, RootDef<Context, {}>>.procedure: ProcedureBuilder<RootDef<Context, {}>>procedure.ProcedureBuilder<RootDef<Context, {}>>.mutation<TrpcError<"OUT_OF_STOCK", undefined>>(resolver: (opts: ResolverOpts<RootDef<Context, {}>>) => TrpcError<"OUT_OF_STOCK", undefined>): BuildProcedure<RootDef<Context, {}>, "mutation", {
output: never;
errors: TrpcError<"OUT_OF_STOCK", undefined>;
services: never;
unmapped: never;
}>
mutation(() => error<"OUT_OF_STOCK", undefined>(opts: Omit<TrpcErrorOptions<"OUT_OF_STOCK", undefined>, "defined">): TrpcError<"OUT_OF_STOCK", undefined>error({ code: "OUT_OF_STOCK"code: 'OUT_OF_STOCK' })); // 400In Effect code
An error's _tag is its code, so Effect code narrows with catchTag:
Effect.catchTag('NOT_FOUND', () => Effect.succeed(null));A failure that isn't a TrpcError is a type error until it is mapped, so internals never reach clients. TrpcError.Tagged classes mark the errors clients may see (Effect).