Skip to content

tRPC v12 isn’t on npm yet. The @trpc12/* packages on this site can’t be installed until it’s released. Use v11 today

tRPC

Procedures

Queries, mutations and subscriptions, with validated input and output.

On this page

A procedure is a function the client can call. Build one from t.procedure and end it with its type: .query() for reads, .mutation() for writes, .subscription() for a stream of events (Subscriptions).

import { import zz } from 'zod';

const 
const byId: Procedure<{
    type: "query";
    ctx: Context;
    meta: {};
    input: {
        id: string;
    };
    output: Post | undefined;
    errors: SchemaValidationError<z.ZodObject<{
        id: z.ZodString;
    }, z.core.$strip>>;
    services: 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 | undefined>(resolver: (opts: ResolverOpts<InputDef<RootDef<Context, {}>, z.ZodObject<{
    id: z.ZodString;
}, z.core.$strip>>>) => Post | undefined): Procedure<{
    type: "query";
    ctx: Context;
    meta: {};
    input: {
        id: string;
    };
    output: Post | undefined;
    errors: SchemaValidationError<z.ZodObject<{
        id: z.ZodString;
    }, z.core.$strip>>;
    services: 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));
const
const create: Procedure<{
    type: "mutation";
    ctx: Context;
    meta: {};
    input: {
        title: string;
    };
    output: Post;
    errors: SchemaValidationError<z.ZodObject<{
        title: z.ZodString;
    }, z.core.$strip>>;
    services: never;
}>
create
= const t: TrpcRoot<Context, {}, RootDef<Context, {}>>t.TrpcRoot<Context, {}, RootDef<Context, {}>>.procedure: ProcedureBuilder<RootDef<Context, {}>>procedure
.
ProcedureBuilder<RootDef<Context, {}>>.input<z.ZodObject<{
    title: z.ZodString;
}, z.core.$strip>>(schema: z.ZodObject<{
    title: z.ZodString;
}, z.core.$strip>): ProcedureBuilder<InputDef<RootDef<Context, {}>, z.ZodObject<{
    title: z.ZodString;
}, z.core.$strip>>> (+1 overload)
input
(import zz.
function object<{
    title: z.ZodString;
}>(shape?: {
    title: 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<{
    title: z.ZodString;
}, z.core.$strip>
object
({ title: z.ZodStringtitle: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() }))
.
ProcedureBuilder<InputDef<RootDef<Context, {}>, ZodObject<{ title: ZodString; }, $strip>>>.mutation<Post>(resolver: (opts: ResolverOpts<InputDef<RootDef<Context, {}>, z.ZodObject<{
    title: z.ZodString;
}, z.core.$strip>>>) => Post): Procedure<{
    type: "mutation";
    ctx: Context;
    meta: {};
    input: {
        title: string;
    };
    output: Post;
    errors: SchemaValidationError<z.ZodObject<{
        title: z.ZodString;
    }, z.core.$strip>>;
    services: never;
}>
mutation
((
opts: ResolverOpts<InputDef<RootDef<Context, {}>, z.ZodObject<{
    title: 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
.
create: (input: {
    title: string;
    author?: string | null;
}) => Post
create
(
opts: ResolverOpts<InputDef<RootDef<Context, {}>, z.ZodObject<{
    title: z.ZodString;
}, z.core.$strip>>>
opts
.
ResolverOpts<InputDef<RootDef<Context, {}>, ZodObject<{ title: ZodString; }, $strip>>>.input: {
    title: string;
}
input
));

A resolver gets opts: the validated input, the ctx (Context), the procedure's meta, path and type, a signal that aborts when the client goes away, a response handle (Responses) and constructors for its declared errors (Errors). It can return a value, a promise, an Effect (Effect) or a stream (Streaming).

Input and output

.input() and .output() take a Standard Schema (Zod, Valibot, ArkType) or an Effect Schema. Several .input() calls merge.

const 
const byId: Procedure<{
    type: "query";
    ctx: Context;
    meta: {};
    input: {
        id: string;
    };
    output: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    } | undefined;
    errors: SchemaValidationError<z.ZodObject<{
        id: z.ZodString;
    }, z.core.$strip>>;
    services: 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>>>.output<z.ZodOptional<z.ZodObject<{
    id: z.ZodString;
    title: z.ZodString;
    author: z.ZodNullable<z.ZodString>;
    createdAt: z.ZodDate;
}, z.core.$strip>>>(schema: z.ZodOptional<z.ZodObject<{
    id: z.ZodString;
    title: z.ZodString;
    author: z.ZodNullable<z.ZodString>;
    createdAt: z.ZodDate;
}, z.core.$strip>>, opts?: {
    validate?: true;
}): ProcedureBuilder<With<InputDef<RootDef<Context, {}>, z.ZodObject<{
    id: z.ZodString;
}, z.core.$strip>>, {
    outputIn: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    } | undefined;
    outputOut: {
        ...;
    } | undefined;
    requires: never;
}>> (+1 overload)
output
(
const PostSchema: z.ZodObject<{
    id: z.ZodString;
    title: z.ZodString;
    author: z.ZodNullable<z.ZodString>;
    createdAt: z.ZodDate;
}, z.core.$strip>
PostSchema
.
ZodType<any, any, $ZodObjectInternals<{ id: ZodString; title: ZodString; author: ZodNullable<ZodString>; createdAt: ZodDate; }, $strip>>.optional(): z.ZodOptional<z.ZodObject<{
    id: z.ZodString;
    title: z.ZodString;
    author: z.ZodNullable<z.ZodString>;
    createdAt: z.ZodDate;
}, z.core.$strip>>
optional
())
.
ProcedureBuilder<With<InputDef<RootDef<Context, {}>, ZodObject<{ id: ZodString; }, $strip>>, { outputIn: { id: string; title: string; author: string | null; createdAt: Date; } | undefined; outputOut: { ...; } | undefined; requires: never; }>>.query<Post | undefined>(resolver: (opts: ResolverOpts<With<InputDef<RootDef<Context, {}>, z.ZodObject<{
    id: z.ZodString;
}, z.core.$strip>>, {
    outputIn: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    } | undefined;
    outputOut: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    } | undefined;
    requires: never;
}>>) => (Post & ({
    id: string;
    title: string;
    author: string | null;
    createdAt: Date;
} | AnyTrpcError | Promise<{
    id: string;
    title: string;
    author: string | null;
    createdAt: Date;
} | AnyTrpcError | undefined> | Effect<...>)) | undefined): Procedure<...>
query
((
opts: ResolverOpts<With<InputDef<RootDef<Context, {}>, z.ZodObject<{
    id: z.ZodString;
}, z.core.$strip>>, {
    outputIn: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    } | undefined;
    outputOut: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    } | undefined;
    requires: never;
}>>
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<With<InputDef<RootDef<Context, {}>, z.ZodObject<{
    id: z.ZodString;
}, z.core.$strip>>, {
    outputIn: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    } | undefined;
    outputOut: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    } | undefined;
    requires: never;
}>>
opts
.
ResolverOpts<With<InputDef<RootDef<Context, {}>, ZodObject<{ id: ZodString; }, $strip>>, { outputIn: { id: string; title: string; author: string | null; createdAt: Date; } | undefined; outputOut: { ...; } | undefined; requires: never; }>>.input: {
    id: string;
}
input
.id: stringid));

The resolver returns the output schema's input type and the client gets its output type, so transforms run on the server. An invalid input fails with a BAD_REQUEST error whose data.issues lists the problems. It is a typed (defined) error, so the client sees the issues in production too (Errors).

Input issues

Each issue has the shape of the library that reported it, and a path typed from the schema's input type: [] for the whole input, then a key or an array index per level.

Zod issues keep zod's fields (code, minimum, format, keys, …), so checking code narrows the rest. Effect only stops at its first issue unless asked otherwise; inputs are decoded with every issue reported, as zod does. A form can match issues to its fields without casts:

import { 
function safe<T, E = unknown>(promise: PromiseLike<T> & {
    readonly "~error"?: E;
}): Promise<SafeResult<T, E>>
safe
} from '@trpc12/trpc/client';
const [,
const err: (TrpcError<"NOT_FOUND", undefined> & {
    readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
    readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
    readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
    readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
    readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
    readonly defined: false;
}) | (TrpcError<"FORBIDDEN", undefined> & {
    readonly defined: false;
}) | ... 20 more ... | undefined
err
] = await
safe<{
    id: string;
    title: string;
    author: string | null;
    createdAt: string;
}, (TrpcError<"NOT_FOUND", undefined> & {
    readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
    readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
    readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
    readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
    readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
    readonly defined: false;
}) | ... 20 more ... | (TrpcError<...> & {
    ...;
})>(promise: PromiseLike<...> & {
    ...;
}): Promise<...>
safe
(
const client: TrpcClient<{
    post: {
        byId: Procedure<{
            type: "query";
            ctx: Context;
            meta: {};
            input: {
                id: string;
            };
            output: Post;
            errors: TrpcError<"NOT_FOUND", {
                id: string;
            }> | SchemaValidationError<ZodObject<{
                id: ZodString;
            }, $strip>>;
            services: never;
        }>;
        list: Procedure<{
            type: "query";
            ctx: Context;
            meta: {};
            input: undefined;
            output: Post[];
            errors: never;
            services: never;
        }>;
        create: Procedure<{
            type: "mutation";
            ctx: Context;
            meta: {};
            input: {
                title: string;
            };
            output: Post;
            errors: TrpcError<...> | SchemaValidationError<...>;
            services: never;
        }>;
    };
}, {}, undefined, LinksTypes<...>>
client
.
post: ClientRouter<{
    byId: Procedure<{
        type: "query";
        ctx: Context;
        meta: {};
        input: {
            id: string;
        };
        output: Post;
        errors: TrpcError<"NOT_FOUND", {
            id: string;
        }> | SchemaValidationError<ZodObject<{
            id: ZodString;
        }, $strip>>;
        services: never;
    }>;
    list: Procedure<{
        type: "query";
        ctx: Context;
        meta: {};
        input: undefined;
        output: Post[];
        errors: never;
        services: never;
    }>;
    create: Procedure<{
        type: "mutation";
        ctx: Context;
        meta: {};
        input: {
            title: string;
        };
        output: Post;
        errors: TrpcError<...> | SchemaValidationError<...>;
        services: never;
    }>;
}, {}, undefined, LinksTypes<...>>
post
.
create: {
    mutate(input: {
        title: string;
    }, opts?: CallOptions<{}> | undefined): TrpcPromise<{
        id: string;
        title: string;
        author: string | null;
        createdAt: string;
    }, CallError<{
        type: "mutation";
        ctx: Context;
        meta: {};
        input: {
            title: string;
        };
        output: Post;
        errors: TrpcError<"UNAUTHORIZED", undefined> | SchemaValidationError<ZodObject<{
            title: ZodString;
        }, $strip>>;
        services: never;
    }, JsonForm, never>>;
}
create
.
function mutate(input: {
    title: string;
}, opts?: CallOptions<{}> | undefined): TrpcPromise<{
    id: string;
    title: string;
    author: string | null;
    createdAt: string;
}, CallError<{
    type: "mutation";
    ctx: Context;
    meta: {};
    input: {
        title: string;
    };
    output: Post;
    errors: TrpcError<"UNAUTHORIZED", undefined> | SchemaValidationError<ZodObject<{
        title: ZodString;
    }, $strip>>;
    services: never;
}, JsonForm, never>>
mutate
({ title: stringtitle: '' }));
if (
const err: (TrpcError<"NOT_FOUND", undefined> & {
    readonly defined: false;
}) | (TrpcError<"BAD_REQUEST", undefined> & {
    readonly defined: false;
}) | (TrpcError<"UNAUTHORIZED", undefined> & {
    readonly defined: false;
}) | (TrpcError<"PARSE_ERROR", undefined> & {
    readonly defined: false;
}) | (TrpcError<"UNSUPPORTED_PROTOCOL", undefined> & {
    readonly defined: false;
}) | (TrpcError<"PAYMENT_REQUIRED", undefined> & {
    readonly defined: false;
}) | (TrpcError<"FORBIDDEN", undefined> & {
    readonly defined: false;
}) | ... 20 more ... | undefined
err
?.defined: boolean | undefineddefined &&
const err: (TrpcError<"UNAUTHORIZED", undefined> & {
    readonly defined: true;
}) | (TrpcError<"BAD_REQUEST", SchemaInputErrorData<ZodObject<{
    title: ZodString;
}, $strip>>> & {
    readonly defined: true;
})
err
.TrpcError<TCode extends string = string, TData = unknown>.code: "BAD_REQUEST" | "UNAUTHORIZED"code === 'BAD_REQUEST') {
for (const
const issue: {
    readonly code: "invalid_type";
    readonly expected: $ZodInvalidTypeExpected;
    readonly path: readonly [] | readonly ["title"];
    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 ["title"];
    readonly message: string;
} | {
    readonly code: "too_small";
    readonly origin: "number" | "int" | "bigint" | "date" | "string" | "array" | "set" | "file" | (string & {});
    ... 4 more ...;
    readonly message: string;
} | ... 8 more ... | {
    ...;
}
issue
of
const err: TrpcError<"BAD_REQUEST", SchemaInputErrorData<ZodObject<{
    title: ZodString;
}, $strip>>> & {
    readonly defined: true;
}
err
.
TrpcError<"BAD_REQUEST", SchemaInputErrorData<ZodObject<{ title: ZodString; }, $strip>>>.data: SchemaInputErrorData<ZodObject<{
    title: ZodString;
}, $strip>>
data
.
SchemaInputErrorData<ZodObject<{ title: ZodString; }, $strip>>.issues: readonly ({
    readonly code: "invalid_type";
    readonly expected: $ZodInvalidTypeExpected;
    readonly path: readonly [] | readonly ["title"];
    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 ["title"];
    readonly message: string;
} | {
    readonly code: "too_small";
    readonly origin: "number" | "int" | "bigint" | "date" | "string" | "array" | "set" | "file" | (string & {});
    ... 4 more ...;
    readonly message: string;
} | ... 8 more ... | {
    ...;
})[]
issues
) {
const issue: {
    readonly code: "invalid_type";
    readonly expected: $ZodInvalidTypeExpected;
    readonly path: readonly [] | readonly ["title"];
    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 ["title"];
    readonly message: string;
} | {
    readonly code: "too_small";
    readonly origin: "number" | "int" | "bigint" | "date" | "string" | "array" | "set" | "file" | (string & {});
    ... 4 more ...;
    readonly message: string;
} | ... 8 more ... | {
    ...;
}
issue
.path;
path: readonly [] | readonly ["title"]
if (
const issue: {
    readonly code: "invalid_type";
    readonly expected: $ZodInvalidTypeExpected;
    readonly path: readonly [] | readonly ["title"];
    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 ["title"];
    readonly message: string;
} | {
    readonly code: "too_small";
    readonly origin: "number" | "int" | "bigint" | "date" | "string" | "array" | "set" | "file" | (string & {});
    ... 4 more ...;
    readonly message: string;
} | ... 8 more ... | {
    ...;
}
issue
.code: "invalid_union" | "invalid_type" | "too_big" | "too_small" | "invalid_format" | "not_multiple_of" | "unrecognized_keys" | "invalid_key" | "invalid_element" | "invalid_value" | "custom"code === 'too_small')
const issue: {
    readonly code: "too_small";
    readonly origin: "number" | "int" | "bigint" | "date" | "string" | "array" | "set" | "file" | (string & {});
    ... 4 more ...;
    readonly message: string;
}
issue
.minimum;
minimum: string | number
} }

Effect Schema issues are the leaves of Effect's issue tree: a _tag (InvalidType, InvalidValue, MissingKey, UnexpectedKey, Forbidden, OneOf or AnyOf), Effect's message, the path, and check when a check failed. A built-in check has its id and settings ('effect/schema/isMinLength', { minLength: 3 }):

import { import SchemaSchema } from 'effect';
import type { type inferInputIssue<S> = S extends StandardSchemaV1<unknown, unknown> ? S extends ZodLike<unknown> ? ZodOrStandardIssue<ZodIssueOf<S>, InOf<S>> : InputIssue<InOf<S>> : EffectInputIssue<InOf<S>>inferInputIssue } from '@trpc12/trpc/server';

const 
const Signup: Schema.Struct<{
    readonly name: Schema.String;
}>
Signup
= import SchemaSchema.
function Struct<{
    readonly name: Schema.String;
}>(fields: {
    readonly name: Schema.String;
}): Schema.Struct<{
    readonly name: Schema.String;
}>
Struct
({
name: Schema.Stringname: import SchemaSchema.const String: Schema.StringString.BottomWithoutNew<string, string, never, never, String, String, string, string, readonly [], string, "readonly", "required", "no-default", "readonly", "required">.check(checks_0: Check<string>, ...checks: Check<string>[]): Schema.Stringcheck(import SchemaSchema.
function isMinLength(minLength: number, annotations?: Schema.Annotations.Filter): Filter<{
    readonly length: number;
}>
isMinLength
(3)),
}); declare const
const issue: EffectInputIssue<{
    readonly name: string;
}>
issue
: type inferInputIssue<S> = S extends StandardSchemaV1<unknown, unknown> ? S extends ZodLike<unknown> ? ZodOrStandardIssue<ZodIssueOf<S>, InOf<S>> : InputIssue<InOf<S>> : EffectInputIssue<InOf<S>>inferInputIssue<typeof
const Signup: Schema.Struct<{
    readonly name: Schema.String;
}>
Signup
>;
const issue: EffectInputIssue<{
    readonly name: string;
}>
issue
._tag;
_tag: "InvalidType" | "InvalidValue" | "MissingKey" | "UnexpectedKey" | "Forbidden" | "OneOf" | "AnyOf"
const issue: EffectInputIssue<{
    readonly name: string;
}>
issue
.path;
path: readonly [] | readonly ["name"]
const issue: EffectInputIssue<{
    readonly name: string;
}>
issue
.
check?: {
    readonly id?: string;
    readonly payload?: unknown;
} | undefined
check
?.id?: string | undefinedid;

Other Standard Schema libraries (Valibot, ArkType, …) report Standard Schema's message and path. inferInputIssue<typeof schema> names a schema's issue type.

Issues are JSON under every serializer: zod's input is dropped (it echoes what the caller sent), bigints and symbols become strings, and undefined in a list becomes null.

The path is what the input type allows. A refinement that reports its own path (zod's refine(…, { path }), an Effect filter returning { path, issue }) should name a key of the input, and a strict object reports unknown keys outside it. With several .input() calls, each schema adds its own BAD_REQUEST, whose issues name its own keys.

Typing without validating

To type and document a result without running the schema, pass { validate: false }. Since no transform runs, the resolver and the client both get the schema's input type:

const 
const latest: Procedure<{
    type: "query";
    ctx: Context;
    meta: {};
    input: undefined;
    output: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    }[];
    errors: never;
    services: never;
}>
latest
= const t: TrpcRoot<Context, {}, RootDef<Context, {}>>t.TrpcRoot<Context, {}, RootDef<Context, {}>>.procedure: ProcedureBuilder<RootDef<Context, {}>>procedure
.
ProcedureBuilder<RootDef<Context, {}>>.output<ZodArray<ZodObject<{
    id: ZodString;
    title: ZodString;
    author: ZodNullable<ZodString>;
    createdAt: ZodDate;
}, $strip>>>(schema: ZodArray<ZodObject<{
    id: ZodString;
    title: ZodString;
    author: ZodNullable<ZodString>;
    createdAt: ZodDate;
}, $strip>>, opts: {
    validate: false;
}): ProcedureBuilder<With<RootDef<Context, {}>, {
    outputIn: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    }[];
    outputOut: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    }[];
}>> (+1 overload)
output
(
const PostSchema: ZodObject<{
    id: ZodString;
    title: ZodString;
    author: ZodNullable<ZodString>;
    createdAt: ZodDate;
}, $strip>
PostSchema
.
ZodType<any, any, $ZodObjectInternals<{ id: ZodString; title: ZodString; author: ZodNullable<ZodString>; createdAt: ZodDate; }, $strip>>.array(): ZodArray<ZodObject<{
    id: ZodString;
    title: ZodString;
    author: ZodNullable<ZodString>;
    createdAt: ZodDate;
}, $strip>>
array
(), { validate: falsevalidate: false })
.
ProcedureBuilder<With<RootDef<Context, {}>, { outputIn: { id: string; title: string; author: string | null; createdAt: Date; }[]; outputOut: { id: string; title: string; author: string | null; createdAt: Date; }[]; }>>.query<Post[]>(resolver: (opts: ResolverOpts<With<RootDef<Context, {}>, {
    outputIn: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    }[];
    outputOut: {
        id: string;
        title: string;
        author: string | null;
        createdAt: Date;
    }[];
}>>) => Post[] & (MaybePromise<{
    id: string;
    title: string;
    author: string | null;
    createdAt: Date;
}[] | AnyTrpcError> | Effect<{
    id: string;
    title: string;
    author: string | null;
    createdAt: Date;
}[] | AnyTrpcError, any, any>)): Procedure<...>
query
(() =>
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
.list: () => Post[]list());

Metadata

.meta() attaches data to a procedure that middleware reads as opts.meta. Meta keys a root doesn't declare are a type error, so declare them on initTrpc:

import { function initTrpc<TConfig extends RootConfig = {}>(opts?: RootOptions<TConfig["meta"] extends object ? TConfig["meta"] : {}>): TrpcRoot<TConfig["ctx"] extends object ? TConfig["ctx"] : {}, TConfig["meta"] extends object ? TConfig["meta"] : {}>initTrpc } from '@trpc12/trpc/server';

const 
const t: TrpcRoot<{}, {
    auth?: boolean;
}, RootDef<{}, {
    auth?: boolean;
}>>
t
=
initTrpc<{
    ctx: {};
    meta: {
        auth?: boolean;
    };
}>(opts?: RootOptions<{
    auth?: boolean;
}>): TrpcRoot<{}, {
    auth?: boolean;
}, RootDef<{}, {
    auth?: boolean;
}>>
initTrpc
<{ ctx: {}ctx: {};
meta: {
    auth?: boolean;
}
meta
: { auth?: boolean | undefinedauth?: boolean } }>();
const
const admin: Procedure<{
    type: "query";
    ctx: {};
    meta: {
        auth?: boolean;
    };
    input: undefined;
    output: string;
    errors: never;
    services: never;
}>
admin
=
const t: TrpcRoot<{}, {
    auth?: boolean;
}, RootDef<{}, {
    auth?: boolean;
}>>
t
.
TrpcRoot<{}, { auth?: boolean; }, RootDef<{}, { auth?: boolean; }>>.procedure: ProcedureBuilder<RootDef<{}, {
    auth?: boolean;
}>>
procedure
.
ProcedureBuilder<RootDef<{}, { auth?: boolean; }>>.meta<{
    readonly auth: true;
}>(meta: {
    readonly auth: true;
} & UndeclaredMeta<{
    auth?: boolean;
}, {
    readonly auth: true;
}>): ProcedureBuilder<RootDef<{}, {
    auth?: boolean;
}>>
meta
({ auth: trueauth: true }).
ProcedureBuilder<RootDef<{}, { auth?: boolean; }>>.query<string>(resolver: (opts: ResolverOpts<RootDef<{}, {
    auth?: boolean;
}>>) => string): Procedure<{
    type: "query";
    ctx: {};
    meta: {
        auth?: boolean;
    };
    input: undefined;
    output: string;
    errors: never;
    services: never;
}>
query
(() => 'secret');

A later .meta() replaces a key unless its root says how to merge it (Extensions).