Skip to content
tRPC

Middlewares

On this page

You can add middleware(s) to a procedure with the t.procedure.use() method. The middleware(s) will wrap the invocation of the procedure and must call opts.next() and return its result.

Authorization

In the example below, any call to a adminProcedure will ensure that the user is an "admin" before executing.

import { class TRPCErrorTRPCError, const initTRPC: TRPCBuilder<object, object>initTRPC } from '@trpc/server';

interface Context {
  
Context.user?: {
    id: string;
    isAdmin: boolean;
} | undefined
user
?: {
id: stringid: string; isAdmin: booleanisAdmin: boolean; // [..] }; } const
const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
= const initTRPC: TRPCBuilder<object, object>initTRPC.TRPCBuilder<object, object>.context<Context>(): TRPCBuilder<Context, object>context<Context>().
TRPCBuilder<Context, object>.create<RuntimeConfigOptions<Context, object>>(opts?: RuntimeConfigOptions<Context, object> | undefined): TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
create
();
export const const publicProcedure: ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure =
const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, { ctx: Context; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure;
export const
const router: RouterBuilder<{
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
router
=
const t: TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, {
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.
TRPCRootObject<Context, object, RuntimeConfigOptions<Context, object>, { ctx: Context; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.router: RouterBuilder<{
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
router
;
export const
const adminProcedure: ProcedureBuilder<Context, object, {
    user: {
        id: string;
        isAdmin: boolean;
    };
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
adminProcedure
= const publicProcedure: ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure.
ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<{
    user: {
        id: string;
        isAdmin: boolean;
    };
}>(fn: MiddlewareBuilder<{
    user: {
        id: string;
        isAdmin: boolean;
    } | undefined;
}, object, {
    user: {
        id: string;
        isAdmin: boolean;
    };
}, UnsetMarker> | MiddlewareFunction<Context, object, object, {
    user: {
        id: string;
        isAdmin: boolean;
    };
}, UnsetMarker>): ProcedureBuilder<Context, object, {
    user: {
        id: string;
        isAdmin: boolean;
    };
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use
(async (
opts: {
    ctx: {
        user: {
            id: string;
            isAdmin: boolean;
        } | undefined;
    };
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
) => {
const {
const ctx: {
    user: {
        id: string;
        isAdmin: boolean;
    } | undefined;
}
ctx
} =
opts: {
    ctx: {
        user: {
            id: string;
            isAdmin: boolean;
        } | undefined;
    };
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
;
if (!
const ctx: {
    user: {
        id: string;
        isAdmin: boolean;
    } | undefined;
}
ctx
.
user: {
    id: string;
    isAdmin: boolean;
} | undefined
user
?.isAdmin: boolean | undefinedisAdmin) {
throw new
new TRPCError(opts: {
    message?: string;
    code: TRPC_ERROR_CODE_KEY;
    cause?: unknown;
}): TRPCError
TRPCError
({ code: "UNAUTHORIZED" | "PARSE_ERROR" | "BAD_REQUEST" | "INTERNAL_SERVER_ERROR" | "NOT_IMPLEMENTED" | "BAD_GATEWAY" | "SERVICE_UNAVAILABLE" | "GATEWAY_TIMEOUT" | "PAYMENT_REQUIRED" | "FORBIDDEN" | "NOT_FOUND" | "METHOD_NOT_SUPPORTED" | "TIMEOUT" | "CONFLICT" | "PRECONDITION_FAILED" | "PAYLOAD_TOO_LARGE" | "UNSUPPORTED_MEDIA_TYPE" | "UNPROCESSABLE_CONTENT" | "PRECONDITION_REQUIRED" | "TOO_MANY_REQUESTS" | "CLIENT_CLOSED_REQUEST"code: 'UNAUTHORIZED' });
} return
opts: {
    ctx: {
        user: {
            id: string;
            isAdmin: boolean;
        } | undefined;
    };
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
.
next: <{
    user: {
        id: string;
        isAdmin: boolean;
    };
}>(opts: {
    ctx?: {
        user: {
            id: string;
            isAdmin: boolean;
        };
    } | undefined;
    input?: unknown;
}) => Promise<MiddlewareResult<{
    user: {
        id: string;
        isAdmin: boolean;
    };
}>> (+2 overloads)
next
({
ctx?: {
    user: {
        id: string;
        isAdmin: boolean;
    };
} | undefined
ctx
: {
user: {
    id: string;
    isAdmin: boolean;
}
user
:
const ctx: {
    user: {
        id: string;
        isAdmin: boolean;
    } | undefined;
}
ctx
.
user: {
    id: string;
    isAdmin: boolean;
}
user
,
}, }); });
import { 
const adminProcedure: ProcedureBuilder<Context, object, {
    user: {
        id: string;
        isAdmin: boolean;
    };
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
adminProcedure
, const publicProcedure: ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure,
const router: RouterBuilder<{
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
router
} from './trpc';
const
const adminRouter: BuiltRouter<{
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    secretPlace: QueryProcedure<{
        input: void;
        output: string;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
adminRouter
=
router<{
    secretPlace: QueryProcedure<{
        input: void;
        output: string;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>(_: {
    secretPlace: QueryProcedure<{
        input: void;
        output: string;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}): BuiltRouter<{
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    secretPlace: QueryProcedure<{
        input: void;
        output: string;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
router
({
secretPlace: QueryProcedure<{
    input: void;
    output: string;
    meta: object;
    errorShape: DefaultErrorShape;
}>
secretPlace
:
const adminProcedure: ProcedureBuilder<Context, object, {
    user: {
        id: string;
        isAdmin: boolean;
    };
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
adminProcedure
.
ProcedureBuilder<Context, object, { user: { id: string; isAdmin: boolean; }; }, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<string>(resolver: ProcedureResolver<Context, object, {
    user: {
        id: string;
        isAdmin: boolean;
    };
}, UnsetMarker, UnsetMarker, string>): QueryProcedure<{
    input: void;
    output: string;
    meta: object;
    errorShape: DefaultErrorShape;
}>
query
(() => 'a key'),
}); export const
const appRouter: BuiltRouter<{
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    foo: QueryProcedure<{
        input: void;
        output: string;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    admin: BuiltRouter<{
        ctx: Context;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        secretPlace: QueryProcedure<{
            input: void;
            output: string;
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>>
appRouter
=
router<{
    foo: QueryProcedure<{
        input: void;
        output: string;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    admin: BuiltRouter<{
        ctx: Context;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        secretPlace: QueryProcedure<{
            input: void;
            output: string;
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}>(_: {
    foo: QueryProcedure<{
        input: void;
        output: string;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    admin: BuiltRouter<{
        ctx: Context;
        meta: object;
        errorShape: DefaultErrorShape;
        transformer: false;
    }, DecorateCreateRouterOptions<{
        secretPlace: QueryProcedure<{
            input: void;
            output: string;
            meta: object;
            errorShape: DefaultErrorShape;
        }>;
    }>>;
}): BuiltRouter<...>
router
({
foo: QueryProcedure<{
    input: void;
    output: string;
    meta: object;
    errorShape: DefaultErrorShape;
}>
foo
: const publicProcedure: ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure.
ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<string>(resolver: ProcedureResolver<Context, object, object, UnsetMarker, UnsetMarker, string>): QueryProcedure<{
    input: void;
    output: string;
    meta: object;
    errorShape: DefaultErrorShape;
}>
query
(() => 'bar'),
admin: BuiltRouter<{
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    secretPlace: QueryProcedure<{
        input: void;
        output: string;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
admin
:
const adminRouter: BuiltRouter<{
    ctx: Context;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    secretPlace: QueryProcedure<{
        input: void;
        output: string;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
adminRouter
,
});

Logging

In the example below timings for queries are logged automatically.


export const const loggedProcedure: ProcedureBuilder<object, object, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>loggedProcedure = const publicProcedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure.ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<object>(fn: MiddlewareBuilder<{}, object, object, UnsetMarker> | MiddlewareFunction<object, object, object, object, UnsetMarker>): ProcedureBuilder<object, object, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>use(async (
opts: {
    ctx: {};
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
) => {
const const start: numberstart = var Date: DateConstructorDate.DateConstructor.now(): numbernow(); const const result: MiddlewareResult<object>result = await
opts: {
    ctx: {};
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
.next: () => Promise<MiddlewareResult<object>> (+2 overloads)next();
const const durationMs: numberdurationMs = var Date: DateConstructorDate.DateConstructor.now(): numbernow() - const start: numberstart; const
const meta: {
    path: string;
    type: "query" | "mutation" | "subscription";
    durationMs: number;
}
meta
= { path: stringpath:
opts: {
    ctx: {};
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
.path: stringpath, type: "query" | "mutation" | "subscription"type:
opts: {
    ctx: {};
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
.type: "query" | "mutation" | "subscription"type, durationMs: numberdurationMs };
const result: MiddlewareResult<object>result.ok: booleanok ? var console: Consoleconsole.Console.log(...data: any[]): voidlog('OK request timing:',
const meta: {
    path: string;
    type: "query" | "mutation" | "subscription";
    durationMs: number;
}
meta
)
: var console: Consoleconsole.Console.error(...data: any[]): voiderror('Non-OK request timing',
const meta: {
    path: string;
    type: "query" | "mutation" | "subscription";
    durationMs: number;
}
meta
);
return const result: MiddlewareResult<object>result; });
import { const loggedProcedure: ProcedureBuilder<object, object, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>loggedProcedure, 
const router: RouterBuilder<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
router
} from './trpc';
export const
const appRouter: BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    foo: QueryProcedure<{
        input: void;
        output: string;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    abc: QueryProcedure<{
        input: void;
        output: string;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
=
router<{
    foo: QueryProcedure<{
        input: void;
        output: string;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    abc: QueryProcedure<{
        input: void;
        output: string;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>(_: {
    foo: QueryProcedure<{
        input: void;
        output: string;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
    abc: QueryProcedure<{
        input: void;
        output: string;
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}): BuiltRouter<{
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<...>>
router
({
foo: QueryProcedure<{
    input: void;
    output: string;
    meta: object;
    errorShape: DefaultErrorShape;
}>
foo
: const loggedProcedure: ProcedureBuilder<object, object, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>loggedProcedure.
ProcedureBuilder<object, object, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<string>(resolver: ProcedureResolver<object, object, {}, UnsetMarker, UnsetMarker, string>): QueryProcedure<{
    input: void;
    output: string;
    meta: object;
    errorShape: DefaultErrorShape;
}>
query
(() => 'bar'),
abc: QueryProcedure<{
    input: void;
    output: string;
    meta: object;
    errorShape: DefaultErrorShape;
}>
abc
: const loggedProcedure: ProcedureBuilder<object, object, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>loggedProcedure.
ProcedureBuilder<object, object, {}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<string>(resolver: ProcedureResolver<object, object, {}, UnsetMarker, UnsetMarker, string>): QueryProcedure<{
    input: void;
    output: string;
    meta: object;
    errorShape: DefaultErrorShape;
}>
query
(() => 'def'),
});

Context Extension

"Context Extension" enables middlewares to dynamically add and override keys on a base procedure's context in a typesafe manner.

Below we have an example of a middleware that changes properties of a context, the changes are then available to all chained consumers, such as other middlewares and procedures:


type 
type Context = {
    user?: {
        id: string;
    };
}
Context
= {
// user is nullable
user?: {
    id: string;
} | undefined
user
?: {
id: stringid: string; }; }; const
const protectedProcedure: ProcedureBuilder<Context, object, {
    user: {
        id: string;
    };
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
protectedProcedure
= const publicProcedure: ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure.
ProcedureBuilder<Context, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<{
    user: {
        id: string;
    };
}>(fn: MiddlewareBuilder<{
    user: {
        id: string;
    } | undefined;
}, object, {
    user: {
        id: string;
    };
}, UnsetMarker> | MiddlewareFunction<Context, object, object, {
    user: {
        id: string;
    };
}, UnsetMarker>): ProcedureBuilder<Context, object, {
    user: {
        id: string;
    };
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use
(async function
function (local function) isAuthed(opts: {
    ctx: {
        user: {
            id: string;
        } | undefined;
    };
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}): Promise<...>
isAuthed
(
opts: {
    ctx: {
        user: {
            id: string;
        } | undefined;
    };
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
) {
const {
const ctx: {
    user: {
        id: string;
    } | undefined;
}
ctx
} =
opts: {
    ctx: {
        user: {
            id: string;
        } | undefined;
    };
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
;
// `ctx.user` is nullable if (!
const ctx: {
    user: {
        id: string;
    } | undefined;
}
ctx
.user) {
user: {
    id: string;
} | undefined
throw new
new TRPCError(opts: {
    message?: string;
    code: TRPC_ERROR_CODE_KEY;
    cause?: unknown;
}): TRPCError
TRPCError
({ code: "UNAUTHORIZED" | "PARSE_ERROR" | "BAD_REQUEST" | "INTERNAL_SERVER_ERROR" | "NOT_IMPLEMENTED" | "BAD_GATEWAY" | "SERVICE_UNAVAILABLE" | "GATEWAY_TIMEOUT" | "PAYMENT_REQUIRED" | "FORBIDDEN" | "NOT_FOUND" | "METHOD_NOT_SUPPORTED" | "TIMEOUT" | "CONFLICT" | "PRECONDITION_FAILED" | "PAYLOAD_TOO_LARGE" | "UNSUPPORTED_MEDIA_TYPE" | "UNPROCESSABLE_CONTENT" | "PRECONDITION_REQUIRED" | "TOO_MANY_REQUESTS" | "CLIENT_CLOSED_REQUEST"code: 'UNAUTHORIZED' });
} return
opts: {
    ctx: {
        user: {
            id: string;
        } | undefined;
    };
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
.
next: <{
    user: {
        id: string;
    };
}>(opts: {
    ctx?: {
        user: {
            id: string;
        };
    } | undefined;
    input?: unknown;
}) => Promise<MiddlewareResult<{
    user: {
        id: string;
    };
}>> (+2 overloads)
next
({
ctx?: {
    user: {
        id: string;
    };
} | undefined
ctx
: {
// ✅ user value is known to be non-null now user:
const ctx: {
    user: {
        id: string;
    } | undefined;
}
ctx
.
user: {
    id: string;
}
user
,
user: {
    id: string;
}
}, }); });
const protectedProcedure: ProcedureBuilder<Context, object, {
    user: {
        id: string;
    };
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
protectedProcedure
.
ProcedureBuilder<Context, object, { user: { id: string; }; }, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<{
    id: string;
}>(resolver: ProcedureResolver<Context, object, {
    user: {
        id: string;
    };
}, UnsetMarker, UnsetMarker, {
    id: string;
}>): QueryProcedure<{
    input: void;
    output: {
        id: string;
    };
    meta: object;
    errorShape: DefaultErrorShape;
}>
query
((
opts: ProcedureResolverOptions<Context, object, {
    user: {
        id: string;
    };
}, UnsetMarker>
opts
) => {
const {
const ctx: {
    user: {
        id: string;
    };
}
ctx
} =
opts: ProcedureResolverOptions<Context, object, {
    user: {
        id: string;
    };
}, UnsetMarker>
opts
;
return ctx.
user: {
    id: string;
}
user
;
const ctx: {
    user: {
        id: string;
    };
}
});

Using .concat() to create reusable middlewares and plugins

tRPC has an API called .concat() which allows you to independently define a partial procedure that can be used with any tRPC instance that matches the context and metadata of the plugin.

This helper primarily targets creating plugins and libraries with tRPC.

// ------------------------------------------------
// 🧩🧩🧩 a library creating a reusable plugin 🧩🧩🧩
// @filename: src/myPlugin.ts

import { const initTRPC: TRPCBuilder<object, object>initTRPC, class TRPCErrorTRPCError } from '@trpc/server';

export function 
function createMyPlugin(): {
    pluginProc: ProcedureBuilder<{}, {}, {
        fromPlugin: "hello from myPlugin";
    }, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>;
}
createMyPlugin
() {
// When creating a plugin for tRPC, you use the same API as creating any other tRPC-app // this is the plugin's root `t`-object const
const t: TRPCRootObject<{}, {}, RuntimeConfigOptions<{}, {}>, {
    ctx: {};
    meta: {};
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
= const initTRPC: TRPCBuilder<object, object>initTRPC
.TRPCBuilder<object, object>.context<{}>(): TRPCBuilder<{}, object>context<{ // the procedure using the plugin will need to extend this context }>() .TRPCBuilder<{}, object>.meta<{}>(): TRPCBuilder<{}, {}>meta<{ // the base `initTRPC`-object of the application using this needs to extend this meta }>() .
TRPCBuilder<{}, {}>.create<RuntimeConfigOptions<{}, {}>>(opts?: RuntimeConfigOptions<{}, {}> | undefined): TRPCRootObject<{}, {}, RuntimeConfigOptions<{}, {}>, {
    ctx: {};
    meta: {};
    errorShape: DefaultErrorShape;
    transformer: false;
}>
create
();
return { // you can also add `.input()` if you want your plugin to do input validation
pluginProc: ProcedureBuilder<{}, {}, {
    fromPlugin: "hello from myPlugin";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
pluginProc
:
const t: TRPCRootObject<{}, {}, RuntimeConfigOptions<{}, {}>, {
    ctx: {};
    meta: {};
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.TRPCRootObject<{}, {}, RuntimeConfigOptions<{}, {}>, { ctx: {}; meta: {}; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<{}, {}, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure.
ProcedureBuilder<{}, {}, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<{
    fromPlugin: "hello from myPlugin";
}>(fn: MiddlewareBuilder<{}, {}, {
    fromPlugin: "hello from myPlugin";
}, UnsetMarker> | MiddlewareFunction<{}, {}, object, {
    fromPlugin: "hello from myPlugin";
}, UnsetMarker>): ProcedureBuilder<{}, {}, {
    fromPlugin: "hello from myPlugin";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use
((
opts: {
    ctx: {};
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: {} | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
) => {
return
opts: {
    ctx: {};
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: {} | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
.
next: <{
    fromPlugin: "hello from myPlugin";
}>(opts: {
    ctx?: {
        fromPlugin: "hello from myPlugin";
    } | undefined;
    input?: unknown;
}) => Promise<MiddlewareResult<{
    fromPlugin: "hello from myPlugin";
}>> (+2 overloads)
next
({
ctx?: {
    fromPlugin: "hello from myPlugin";
} | undefined
ctx
: {
fromPlugin: "hello from myPlugin"fromPlugin: 'hello from myPlugin' as type const = "hello from myPlugin"const, }, }); }), }; } // ------------------------------------ // 🚀🚀🚀 the app using the plugin 🚀🚀🚀 // @filename: src/app.ts import {
function createMyPlugin(): {
    pluginProc: ProcedureBuilder<{}, {}, {
        fromPlugin: "hello from myPlugin";
    }, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>;
}
createMyPlugin
} from './myPlugin';
import { const initTRPC: TRPCBuilder<object, object>initTRPC, class TRPCErrorTRPCError } from '@trpc/server'; // the app's root `t`-object const
const t: TRPCRootObject<{}, object, RuntimeConfigOptions<{}, object>, {
    ctx: {};
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
= const initTRPC: TRPCBuilder<object, object>initTRPC
.TRPCBuilder<object, object>.context<{}>(): TRPCBuilder<{}, object>context<{ // ... }>() .
TRPCBuilder<{}, object>.create<RuntimeConfigOptions<{}, object>>(opts?: RuntimeConfigOptions<{}, object> | undefined): TRPCRootObject<{}, object, RuntimeConfigOptions<{}, object>, {
    ctx: {};
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
create
();
export const const publicProcedure: ProcedureBuilder<{}, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure =
const t: TRPCRootObject<{}, object, RuntimeConfigOptions<{}, object>, {
    ctx: {};
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.TRPCRootObject<{}, object, RuntimeConfigOptions<{}, object>, { ctx: {}; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<{}, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure;
export const
const router: RouterBuilder<{
    ctx: {};
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
router
=
const t: TRPCRootObject<{}, object, RuntimeConfigOptions<{}, object>, {
    ctx: {};
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.
TRPCRootObject<{}, object, RuntimeConfigOptions<{}, object>, { ctx: {}; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.router: RouterBuilder<{
    ctx: {};
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
router
;
// initialize the plugin (a real-world example would likely take options here) const
const plugin: {
    pluginProc: ProcedureBuilder<{}, {}, {
        fromPlugin: "hello from myPlugin";
    }, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>;
}
plugin
=
function createMyPlugin(): {
    pluginProc: ProcedureBuilder<{}, {}, {
        fromPlugin: "hello from myPlugin";
    }, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>;
}
createMyPlugin
();
// create a base procedure using the plugin const
const procedureWithPlugin: ProcedureBuilder<{}, object, {
    fromPlugin: "hello from myPlugin";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
procedureWithPlugin
= const publicProcedure: ProcedureBuilder<{}, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure
.
ProcedureBuilder<{}, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.concat<{}, {}, {
    fromPlugin: "hello from myPlugin";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, DefaultErrorShape>(builder: ProcedureBuilder<{}, {}, {
    fromPlugin: "hello from myPlugin";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>): ProcedureBuilder<{}, object, {
    fromPlugin: "hello from myPlugin";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
concat
(
const plugin: {
    pluginProc: ProcedureBuilder<{}, {}, {
        fromPlugin: "hello from myPlugin";
    }, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>;
}
plugin
.
pluginProc: ProcedureBuilder<{}, {}, {
    fromPlugin: "hello from myPlugin";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
pluginProc
,
) .
ProcedureBuilder<{}, object, { fromPlugin: "hello from myPlugin"; }, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<{
    fromPlugin: "hello from myPlugin";
}>(fn: MiddlewareBuilder<{
    fromPlugin: "hello from myPlugin";
}, object, {
    fromPlugin: "hello from myPlugin";
}, UnsetMarker> | MiddlewareFunction<{}, object, {
    fromPlugin: "hello from myPlugin";
}, {
    fromPlugin: "hello from myPlugin";
}, UnsetMarker>): ProcedureBuilder<{}, object, {
    fromPlugin: "hello from myPlugin";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use
(
opts: {
    ctx: {
        fromPlugin: "hello from myPlugin";
    };
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<{
            fromPlugin: "hello from myPlugin";
        }>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
=> {
const { ctx } =
opts: {
    ctx: {
        fromPlugin: "hello from myPlugin";
    };
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<{
            fromPlugin: "hello from myPlugin";
        }>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
;
const ctx: {
    fromPlugin: "hello from myPlugin";
}
return
opts: {
    ctx: {
        fromPlugin: "hello from myPlugin";
    };
    type: ProcedureType;
    path: string;
    input: UnsetMarker;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<{
            fromPlugin: "hello from myPlugin";
        }>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
.
next: () => Promise<MiddlewareResult<{
    fromPlugin: "hello from myPlugin";
}>> (+2 overloads)
next
()
}) export const
const appRouter: BuiltRouter<{
    ctx: {};
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: void;
        output: "hello from myPlugin";
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
appRouter
=
const router: RouterBuilder
<{
    hello: QueryProcedure<{
        input: void;
        output: "hello from myPlugin";
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>(_: {
    hello: QueryProcedure<{
        input: void;
        output: "hello from myPlugin";
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}) => BuiltRouter<{
    ctx: {};
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}, DecorateCreateRouterOptions<{
    hello: QueryProcedure<{
        input: void;
        output: "hello from myPlugin";
        meta: object;
        errorShape: DefaultErrorShape;
    }>;
}>>
router
({
hello: QueryProcedure<{
    input: void;
    output: "hello from myPlugin";
    meta: object;
    errorShape: DefaultErrorShape;
}>
hello
:
const procedureWithPlugin: ProcedureBuilder<{}, object, {
    fromPlugin: "hello from myPlugin";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
procedureWithPlugin
.
ProcedureBuilder<{}, object, { fromPlugin: "hello from myPlugin"; }, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<"hello from myPlugin">(resolver: ProcedureResolver<{}, object, {
    fromPlugin: "hello from myPlugin";
}, UnsetMarker, UnsetMarker, "hello from myPlugin">): QueryProcedure<{
    input: void;
    output: "hello from myPlugin";
    meta: object;
    errorShape: DefaultErrorShape;
}>
query
(
opts: ProcedureResolverOptions<{}, object, {
    fromPlugin: "hello from myPlugin";
}, UnsetMarker>
opts
=> {
return
opts: ProcedureResolverOptions<{}, object, {
    fromPlugin: "hello from myPlugin";
}, UnsetMarker>
opts
.
ProcedureResolverOptions<{}, object, { fromPlugin: "hello from myPlugin"; }, UnsetMarker>.ctx: {
    fromPlugin: "hello from myPlugin";
}
ctx
.fromPlugin: "hello from myPlugin"fromPlugin;
}) })

Extending middlewares

We have a powerful feature called .pipe() which allows you to extend middlewares in a typesafe manner.

Below we have an example of a middleware that extends a base middleware(foo). Like the context extension example above, piping middlewares will change properties of the context, and procedures will receive the new context value.


const 
const fooMiddleware: MiddlewareBuilder<object, object, {
    foo: "foo";
}, unknown>
fooMiddleware
=
const t: TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.
TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, { ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.middleware: <{
    foo: "foo";
}>(fn: MiddlewareFunction<object, object, object, {
    foo: "foo";
}, unknown>) => MiddlewareBuilder<object, object, {
    foo: "foo";
}, unknown>
middleware
((
opts: {
    ctx: {};
    type: ProcedureType;
    path: string;
    input: unknown;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
) => {
return
opts: {
    ctx: {};
    type: ProcedureType;
    path: string;
    input: unknown;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
.
next: <{
    foo: "foo";
}>(opts: {
    ctx?: {
        foo: "foo";
    } | undefined;
    input?: unknown;
}) => Promise<MiddlewareResult<{
    foo: "foo";
}>> (+2 overloads)
next
({
ctx?: {
    foo: "foo";
} | undefined
ctx
: {
foo: "foo"foo: 'foo' as type const = "foo"const, }, }); }); const
const barMiddleware: MiddlewareBuilder<object, object, {
    foo: "foo";
    bar: "bar";
}, unknown>
barMiddleware
=
const fooMiddleware: MiddlewareBuilder<object, object, {
    foo: "foo";
}, unknown>
fooMiddleware
.
MiddlewareBuilder<object, object, { foo: "foo"; }, unknown>.unstable_pipe<{
    bar: "bar";
}>(fn: MiddlewareFunction<object, object, {
    foo: "foo";
}, {
    bar: "bar";
}, unknown> | MiddlewareBuilder<{
    foo: "foo";
}, object, {
    bar: "bar";
}, unknown>): MiddlewareBuilder<object, object, {
    foo: "foo";
    bar: "bar";
}, unknown>
unstable_pipe
((
opts: {
    ctx: {
        foo: "foo";
    };
    type: ProcedureType;
    path: string;
    input: unknown;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<{
            foo: "foo";
        }>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
) => {
const {
const ctx: {
    foo: "foo";
}
ctx
} =
opts: {
    ctx: {
        foo: "foo";
    };
    type: ProcedureType;
    path: string;
    input: unknown;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<{
            foo: "foo";
        }>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
;
const ctx: {
    foo: "foo";
}
ctx
.foo;
foo: "foo"
return
opts: {
    ctx: {
        foo: "foo";
    };
    type: ProcedureType;
    path: string;
    input: unknown;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<{
            foo: "foo";
        }>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
.
next: <{
    bar: "bar";
}>(opts: {
    ctx?: {
        bar: "bar";
    } | undefined;
    input?: unknown;
}) => Promise<MiddlewareResult<{
    bar: "bar";
}>> (+2 overloads)
next
({
ctx?: {
    bar: "bar";
} | undefined
ctx
: {
bar: "bar"bar: 'bar' as type const = "bar"const, }, }); }); const
const barProcedure: ProcedureBuilder<object, object, {
    foo: "foo";
    bar: "bar";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
barProcedure
= const publicProcedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>publicProcedure.
ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<{
    foo: "foo";
    bar: "bar";
}>(fn: MiddlewareBuilder<{}, object, {
    foo: "foo";
    bar: "bar";
}, UnsetMarker> | MiddlewareFunction<object, object, object, {
    foo: "foo";
    bar: "bar";
}, UnsetMarker>): ProcedureBuilder<object, object, {
    foo: "foo";
    bar: "bar";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use
(
const barMiddleware: MiddlewareBuilder<object, object, {
    foo: "foo";
    bar: "bar";
}, unknown>
barMiddleware
);
const barProcedure: ProcedureBuilder<object, object, {
    foo: "foo";
    bar: "bar";
}, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
barProcedure
.
ProcedureBuilder<object, object, { foo: "foo"; bar: "bar"; }, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<"bar">(resolver: ProcedureResolver<object, object, {
    foo: "foo";
    bar: "bar";
}, UnsetMarker, UnsetMarker, "bar">): QueryProcedure<{
    input: void;
    output: "bar";
    meta: object;
    errorShape: DefaultErrorShape;
}>
query
((
opts: ProcedureResolverOptions<object, object, {
    foo: "foo";
    bar: "bar";
}, UnsetMarker>
opts
) => {
const {
const ctx: {
    foo: "foo";
    bar: "bar";
}
ctx
} =
opts: ProcedureResolverOptions<object, object, {
    foo: "foo";
    bar: "bar";
}, UnsetMarker>
opts
;
return ctx.bar: "bar"bar;
const ctx: {
    foo: "foo";
    bar: "bar";
}
});

Beware that the order in which you pipe your middlewares matter and that the context must overlap. An example of a forbidden pipe is shown below. Here, the fooMiddleware overrides the ctx.a while barMiddleware still expects the root context from the initialization in initTRPC - so piping fooMiddleware with barMiddleware would not work, while piping barMiddleware with fooMiddleware does work.

import { const initTRPC: TRPCBuilder<object, object>initTRPC } from '@trpc/server';

const 
const t: TRPCRootObject<{
    a: {
        b: "a";
    };
}, object, RuntimeConfigOptions<{
    a: {
        b: "a";
    };
}, object>, {
    ctx: {
        a: {
            b: "a";
        };
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
= const initTRPC: TRPCBuilder<object, object>initTRPC
.
TRPCBuilder<object, object>.context<{
    a: {
        b: "a";
    };
}>(): TRPCBuilder<{
    a: {
        b: "a";
    };
}, object>
context
<{
a: {
    b: "a";
}
a
: {
b: "a"b: 'a'; }; }>() .
TRPCBuilder<{ a: { b: "a"; }; }, object>.create<RuntimeConfigOptions<{
    a: {
        b: "a";
    };
}, object>>(opts?: RuntimeConfigOptions<{
    a: {
        b: "a";
    };
}, object> | undefined): TRPCRootObject<{
    a: {
        b: "a";
    };
}, object, RuntimeConfigOptions<{
    a: {
        b: "a";
    };
}, object>, {
    ctx: {
        a: {
            b: "a";
        };
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
create
();
const
const fooMiddleware: MiddlewareBuilder<{
    a: {
        b: "a";
    };
}, object, {
    a: "a";
}, unknown>
fooMiddleware
=
const t: TRPCRootObject<{
    a: {
        b: "a";
    };
}, object, RuntimeConfigOptions<{
    a: {
        b: "a";
    };
}, object>, {
    ctx: {
        a: {
            b: "a";
        };
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.
TRPCRootObject<{ a: { b: "a"; }; }, object, RuntimeConfigOptions<{ a: { b: "a"; }; }, object>, { ctx: { a: { b: "a"; }; }; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.middleware: <{
    a: "a";
}>(fn: MiddlewareFunction<{
    a: {
        b: "a";
    };
}, object, object, {
    a: "a";
}, unknown>) => MiddlewareBuilder<{
    a: {
        b: "a";
    };
}, object, {
    a: "a";
}, unknown>
middleware
((
opts: {
    ctx: {
        a: {
            b: "a";
        };
    };
    type: ProcedureType;
    path: string;
    input: unknown;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
) => {
const {
const ctx: {
    a: {
        b: "a";
    };
}
ctx
} =
opts: {
    ctx: {
        a: {
            b: "a";
        };
    };
    type: ProcedureType;
    path: string;
    input: unknown;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
;
const ctx: {
    a: {
        b: "a";
    };
}
ctx
.a; // 👈 fooMiddleware expects `ctx.a` to be an object
a: {
    b: "a";
}
return
opts: {
    ctx: {
        a: {
            b: "a";
        };
    };
    type: ProcedureType;
    path: string;
    input: unknown;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
.
next: <{
    a: "a";
}>(opts: {
    ctx?: {
        a: "a";
    } | undefined;
    input?: unknown;
}) => Promise<MiddlewareResult<{
    a: "a";
}>> (+2 overloads)
next
({
ctx?: {
    a: "a";
} | undefined
ctx
: {
a: "a"a: 'a' as type const = "a"const, // 👈 `ctx.a` is no longer an object }, }); }); const
const barMiddleware: MiddlewareBuilder<{
    a: {
        b: "a";
    };
}, object, {
    foo: "foo";
}, unknown>
barMiddleware
=
const t: TRPCRootObject<{
    a: {
        b: "a";
    };
}, object, RuntimeConfigOptions<{
    a: {
        b: "a";
    };
}, object>, {
    ctx: {
        a: {
            b: "a";
        };
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.
TRPCRootObject<{ a: { b: "a"; }; }, object, RuntimeConfigOptions<{ a: { b: "a"; }; }, object>, { ctx: { a: { b: "a"; }; }; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.middleware: <{
    foo: "foo";
}>(fn: MiddlewareFunction<{
    a: {
        b: "a";
    };
}, object, object, {
    foo: "foo";
}, unknown>) => MiddlewareBuilder<{
    a: {
        b: "a";
    };
}, object, {
    foo: "foo";
}, unknown>
middleware
((
opts: {
    ctx: {
        a: {
            b: "a";
        };
    };
    type: ProcedureType;
    path: string;
    input: unknown;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
) => {
const {
const ctx: {
    a: {
        b: "a";
    };
}
ctx
} =
opts: {
    ctx: {
        a: {
            b: "a";
        };
    };
    type: ProcedureType;
    path: string;
    input: unknown;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
;
const ctx: {
    a: {
        b: "a";
    };
}
ctx
.a; // 👈 barMiddleware expects `ctx.a` to be an object
a: {
    b: "a";
}
return
opts: {
    ctx: {
        a: {
            b: "a";
        };
    };
    type: ProcedureType;
    path: string;
    input: unknown;
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
.
next: <{
    foo: "foo";
}>(opts: {
    ctx?: {
        foo: "foo";
    } | undefined;
    input?: unknown;
}) => Promise<MiddlewareResult<{
    foo: "foo";
}>> (+2 overloads)
next
({
ctx?: {
    foo: "foo";
} | undefined
ctx
: {
foo: "foo"foo: 'foo' as type const = "foo"const, }, }); }); // ❌ `ctx.a` does not overlap from `fooMiddleware` to `barMiddleware`
const fooMiddleware: MiddlewareBuilder<{
    a: {
        b: "a";
    };
}, object, {
    a: "a";
}, unknown>
fooMiddleware
.
MiddlewareBuilder<{ a: { b: "a"; }; }, object, { a: "a"; }, unknown>.unstable_pipe<{
    foo: "foo";
}>(fn: MiddlewareFunction<{
    a: {
        b: "a";
    };
}, object, {
    a: "a";
}, {
    foo: "foo";
}, unknown> | MiddlewareBuilder<{
    a: "a";
}, object, {
    foo: "foo";
}, unknown>): MiddlewareBuilder<{
    a: {
        b: "a";
    };
}, object, {
    a: "a";
    foo: "foo";
}, unknown>
unstable_pipe
(barMiddleware);
Argument of type 'MiddlewareBuilder<{ a: { b: "a"; }; }, object, { foo: "foo"; }, unknown>' is not assignable to parameter of type 'MiddlewareFunction<{ a: { b: "a"; }; }, object, { a: "a"; }, { foo: "foo"; }, unknown> | MiddlewareBuilder<{ a: "a"; }, object, { foo: "foo"; }, unknown>'. Type 'MiddlewareBuilder<{ a: { b: "a"; }; }, object, { foo: "foo"; }, unknown>' is not assignable to type 'MiddlewareBuilder<{ a: "a"; }, object, { foo: "foo"; }, unknown>'. Types of property 'unstable_pipe' are incompatible. Type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ a: { b: "a"; }; }, object, { foo: "foo"; }, $ContextOverridesOut, unknown> | MiddlewareBuilder<{ a: { b: "a"; }; foo: "foo"; }, object, $ContextOverridesOut, unknown>) => MiddlewareBuilder<...>' is not assignable to type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ a: "a"; }, object, { foo: "foo"; }, $ContextOverridesOut, unknown> | MiddlewareBuilder<{ a: "a"; foo: "foo"; }, object, $ContextOverridesOut, unknown>) => MiddlewareBuilder<...>'. Types of parameters 'fn' and 'fn' are incompatible. Type 'MiddlewareFunction<{ a: "a"; }, object, { foo: "foo"; }, any, unknown> | MiddlewareBuilder<{ a: "a"; foo: "foo"; }, object, any, unknown>' is not assignable to type 'MiddlewareFunction<{ a: { b: "a"; }; }, object, { foo: "foo"; }, any, unknown> | MiddlewareBuilder<{ a: { b: "a"; }; foo: "foo"; }, object, any, unknown>'. Type 'MiddlewareFunction<{ a: "a"; }, object, { foo: "foo"; }, any, unknown>' is not assignable to type 'MiddlewareFunction<{ a: { b: "a"; }; }, object, { foo: "foo"; }, any, unknown> | MiddlewareBuilder<{ a: { b: "a"; }; foo: "foo"; }, object, any, unknown>'. Type 'MiddlewareFunction<{ a: "a"; }, object, { foo: "foo"; }, any, unknown>' is not assignable to type 'MiddlewareFunction<{ a: { b: "a"; }; }, object, { foo: "foo"; }, any, unknown>'. Types of parameters 'opts' and 'opts' are incompatible. Type '{ ctx: { a: { b: "a"; }; foo: "foo"; }; type: "query" | "mutation" | "subscription"; path: string; input: unknown; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; batchIndex: number; next: { ...; }; }' is not assignable to type '{ ctx: { a: "a"; foo: "foo"; }; type: "query" | "mutation" | "subscription"; path: string; input: unknown; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; batchIndex: number; next: { ...; }; }'. The types of 'ctx.a' are incompatible between these types. Type '{ b: "a"; }' is not assignable to type '"a"'.
// ✅ `ctx.a` overlaps from `barMiddleware` and `fooMiddleware`
const barMiddleware: MiddlewareBuilder<{
    a: {
        b: "a";
    };
}, object, {
    foo: "foo";
}, unknown>
barMiddleware
.
MiddlewareBuilder<{ a: { b: "a"; }; }, object, { foo: "foo"; }, unknown>.unstable_pipe<{
    a: "a";
}>(fn: MiddlewareFunction<{
    a: {
        b: "a";
    };
}, object, {
    foo: "foo";
}, {
    a: "a";
}, unknown> | MiddlewareBuilder<{
    a: {
        b: "a";
    };
    foo: "foo";
}, object, {
    a: "a";
}, unknown>): MiddlewareBuilder<{
    a: {
        b: "a";
    };
}, object, {
    a: "a";
    foo: "foo";
}, unknown>
unstable_pipe
(
const fooMiddleware: MiddlewareBuilder<{
    a: {
        b: "a";
    };
}, object, {
    a: "a";
}, unknown>
fooMiddleware
);

Experimental: standalone middlewares

tRPC has an experimental API called experimental_standaloneMiddleware which allows you to independently define a middleware that can be used with any tRPC instance. Creating middlewares using t.middleware has the limitation that the Context type is tied to the Context type of the tRPC instance. This means that you cannot use the same middleware with multiple tRPC instances that have different Context types.

Using experimental_standaloneMiddleware you can create a middleware that explicitly defines its requirements, i.e. the Context, Input and Meta types:

import {
  
const experimental_standaloneMiddleware: <TCtx extends {
    ctx?: object;
    meta?: object;
    input?: unknown;
}>() => {
    create: <$ContextOverrides>(fn: MiddlewareFunction<TCtx extends {
        ctx: infer T extends object;
    } ? T : any, TCtx extends {
        meta: infer T_1 extends object;
    } ? T_1 : object, object, $ContextOverrides, TCtx extends {
        input: infer T_2;
    } ? T_2 : unknown>) => MiddlewareBuilder<TCtx extends {
        ctx: infer T extends object;
    } ? T : any, TCtx extends {
        meta: infer T_1 extends object;
    } ? T_1 : object, $ContextOverrides, TCtx extends {
        input: infer T_2;
    } ? T_2 : unknown>;
}
experimental_standaloneMiddleware
,
const initTRPC: TRPCBuilder<object, object>initTRPC, class TRPCErrorTRPCError, } from '@trpc/server'; import * as import zz from 'zod'; const
const projectAccessMiddleware: MiddlewareBuilder<{
    allowedProjects: string[];
}, object, object, {
    projectId: string;
}>
projectAccessMiddleware
=
experimental_standaloneMiddleware<{
    ctx: {
        allowedProjects: string[];
    };
    input: {
        projectId: string;
    };
}>(): {
    create: <$ContextOverrides>(fn: MiddlewareFunction<{
        allowedProjects: string[];
    }, object, object, $ContextOverrides, {
        projectId: string;
    }>) => MiddlewareBuilder<{
        allowedProjects: string[];
    }, object, $ContextOverrides, {
        projectId: string;
    }>;
}
experimental_standaloneMiddleware
<{
ctx: {
    allowedProjects: string[];
}
ctx
: { allowedProjects: string[]allowedProjects: string[] }; // defaults to 'object' if not defined
input: {
    projectId: string;
}
input
: { projectId: stringprojectId: string }; // defaults to 'unknown' if not defined
// 'meta', not defined here, defaults to 'object | undefined' }>().
create: <object>(fn: MiddlewareFunction<{
    allowedProjects: string[];
}, object, object, object, {
    projectId: string;
}>) => MiddlewareBuilder<{
    allowedProjects: string[];
}, object, object, {
    projectId: string;
}>
create
((
opts: {
    ctx: {
        allowedProjects: string[];
    };
    type: ProcedureType;
    path: string;
    input: {
        projectId: string;
    };
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
) => {
if (!
opts: {
    ctx: {
        allowedProjects: string[];
    };
    type: ProcedureType;
    path: string;
    input: {
        projectId: string;
    };
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
.
ctx: {
    allowedProjects: string[];
}
ctx
.allowedProjects: string[]allowedProjects.Array<string>.includes(searchElement: string, fromIndex?: number): booleanincludes(
opts: {
    ctx: {
        allowedProjects: string[];
    };
    type: ProcedureType;
    path: string;
    input: {
        projectId: string;
    };
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
.
input: {
    projectId: string;
}
input
.projectId: stringprojectId)) {
throw new
new TRPCError(opts: {
    message?: string;
    code: TRPC_ERROR_CODE_KEY;
    cause?: unknown;
}): TRPCError
TRPCError
({
code: "FORBIDDEN" | "PARSE_ERROR" | "BAD_REQUEST" | "INTERNAL_SERVER_ERROR" | "NOT_IMPLEMENTED" | "BAD_GATEWAY" | "SERVICE_UNAVAILABLE" | "GATEWAY_TIMEOUT" | "UNAUTHORIZED" | "PAYMENT_REQUIRED" | "NOT_FOUND" | "METHOD_NOT_SUPPORTED" | "TIMEOUT" | "CONFLICT" | "PRECONDITION_FAILED" | "PAYLOAD_TOO_LARGE" | "UNSUPPORTED_MEDIA_TYPE" | "UNPROCESSABLE_CONTENT" | "PRECONDITION_REQUIRED" | "TOO_MANY_REQUESTS" | "CLIENT_CLOSED_REQUEST"code: 'FORBIDDEN', message?: string | undefinedmessage: 'Not allowed', }); } return
opts: {
    ctx: {
        allowedProjects: string[];
    };
    type: ProcedureType;
    path: string;
    input: {
        projectId: string;
    };
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
.next: () => Promise<MiddlewareResult<object>> (+2 overloads)next();
}); const
const t1: TRPCRootObject<{
    allowedProjects: string[];
}, object, RuntimeConfigOptions<{
    allowedProjects: string[];
}, object>, {
    ctx: {
        allowedProjects: string[];
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t1
= const initTRPC: TRPCBuilder<object, object>initTRPC
.
TRPCBuilder<object, object>.context<{
    allowedProjects: string[];
}>(): TRPCBuilder<{
    allowedProjects: string[];
}, object>
context
<{
allowedProjects: string[]allowedProjects: string[]; }>() .
TRPCBuilder<{ allowedProjects: string[]; }, object>.create<RuntimeConfigOptions<{
    allowedProjects: string[];
}, object>>(opts?: RuntimeConfigOptions<{
    allowedProjects: string[];
}, object> | undefined): TRPCRootObject<{
    allowedProjects: string[];
}, object, RuntimeConfigOptions<{
    allowedProjects: string[];
}, object>, {
    ctx: {
        allowedProjects: string[];
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
create
();
// ✅ `ctx.allowedProjects` satisfies "string[]" and `input.projectId` satisfies "string" const
const accessControlledProcedure: ProcedureBuilder<{
    allowedProjects: string[];
}, object, {}, {
    projectId: string;
}, {
    projectId: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
accessControlledProcedure
=
const t1: TRPCRootObject<{
    allowedProjects: string[];
}, object, RuntimeConfigOptions<{
    allowedProjects: string[];
}, object>, {
    ctx: {
        allowedProjects: string[];
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t1
.
TRPCRootObject<{ allowedProjects: string[]; }, object, RuntimeConfigOptions<{ allowedProjects: string[]; }, object>, { ctx: { allowedProjects: string[]; }; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<{
    allowedProjects: string[];
}, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
procedure
.
ProcedureBuilder<{ allowedProjects: string[]; }, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.input<z.ZodObject<{
    projectId: z.ZodString;
}, z.core.$strip>>(schema: z.ZodObject<{
    projectId: z.ZodString;
}, z.core.$strip>): ProcedureBuilder<{
    allowedProjects: string[];
}, object, object, {
    projectId: string;
}, {
    projectId: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
input
(import zz.
function object<{
    projectId: z.ZodString;
}>(shape?: {
    projectId: z.ZodString;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueUnrecognizedKeys | z.core.$ZodIssueInvalidType<unknown>>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    projectId: z.ZodString;
}, z.core.$strip>
object
({ projectId: z.ZodStringprojectId: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() }))
.
ProcedureBuilder<{ allowedProjects: string[]; }, object, object, { projectId: string; }, { projectId: string; }, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<object>(fn: MiddlewareBuilder<{
    allowedProjects: string[];
}, object, object, {
    projectId: string;
}> | MiddlewareFunction<{
    allowedProjects: string[];
}, object, object, object, {
    projectId: string;
}>): ProcedureBuilder<{
    allowedProjects: string[];
}, object, {}, {
    projectId: string;
}, {
    projectId: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use
(
const projectAccessMiddleware: MiddlewareBuilder<{
    allowedProjects: string[];
}, object, object, {
    projectId: string;
}>
projectAccessMiddleware
);
// ❌ `ctx.allowedProjects` satisfies "string[]" but `input.projectId` does not satisfy "string" const
const accessControlledProcedure2: ProcedureBuilder<{
    allowedProjects: string[];
}, object, {}, {
    projectId: number;
}, {
    projectId: number;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
accessControlledProcedure2
=
const t1: TRPCRootObject<{
    allowedProjects: string[];
}, object, RuntimeConfigOptions<{
    allowedProjects: string[];
}, object>, {
    ctx: {
        allowedProjects: string[];
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t1
.
TRPCRootObject<{ allowedProjects: string[]; }, object, RuntimeConfigOptions<{ allowedProjects: string[]; }, object>, { ctx: { allowedProjects: string[]; }; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<{
    allowedProjects: string[];
}, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
procedure
.
ProcedureBuilder<{ allowedProjects: string[]; }, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.input<z.ZodObject<{
    projectId: z.ZodNumber;
}, z.core.$strip>>(schema: z.ZodObject<{
    projectId: z.ZodNumber;
}, z.core.$strip>): ProcedureBuilder<{
    allowedProjects: string[];
}, object, object, {
    projectId: number;
}, {
    projectId: number;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
input
(import zz.
function object<{
    projectId: z.ZodNumber;
}>(shape?: {
    projectId: z.ZodNumber;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueUnrecognizedKeys | z.core.$ZodIssueInvalidType<unknown>>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    projectId: z.ZodNumber;
}, z.core.$strip>
object
({ projectId: z.ZodNumberprojectId: import zz.function number(params?: string | z.core.$ZodNumberParams): z.ZodNumbernumber() }))
.
ProcedureBuilder<{ allowedProjects: string[]; }, object, object, { projectId: number; }, { projectId: number; }, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<object>(fn: MiddlewareBuilder<{
    allowedProjects: string[];
}, object, object, {
    projectId: number;
}> | MiddlewareFunction<{
    allowedProjects: string[];
}, object, object, object, {
    projectId: number;
}>): ProcedureBuilder<{
    allowedProjects: string[];
}, object, {}, {
    projectId: number;
}, {
    projectId: number;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use
(projectAccessMiddleware);
Argument of type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: string; }>' is not assignable to parameter of type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: number; }> | MiddlewareFunction<{ allowedProjects: string[]; }, object, object, object, { projectId: number; }>'. Type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: string; }>' is not assignable to type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: number; }>'. Types of property 'unstable_pipe' are incompatible. Type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ allowedProjects: string[]; }, object, object, $ContextOverridesOut, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, $ContextOverridesOut, { ...; }>) => MiddlewareBuilder<...>' is not assignable to type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ allowedProjects: string[]; }, object, object, $ContextOverridesOut, { projectId: number; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, $ContextOverridesOut, { ...; }>) => MiddlewareBuilder<...>'. Types of parameters 'fn' and 'fn' are incompatible. Type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: number; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, any, { projectId: number; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, any, { projectId: string; }>'. Type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: number; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, any, { projectId: string; }>'. Type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: number; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }>'. Types of parameters 'opts' and 'opts' are incompatible. Type '{ ctx: { allowedProjects: string[]; }; type: "query" | "mutation" | "subscription"; path: string; input: { projectId: string; }; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; batchIndex: number; next: { ...; }; }' is not assignable to type '{ ctx: { allowedProjects: string[]; }; type: "query" | "mutation" | "subscription"; path: string; input: { projectId: number; }; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; batchIndex: number; next: { ...; }; }'. The types of 'input.projectId' are incompatible between these types. Type 'string' is not assignable to type 'number'.
// ❌ `ctx.allowedProjects` does not satisfy "string[]" even though `input.projectId` satisfies "string" const
const t2: TRPCRootObject<{
    allowedProjects: number[];
}, object, RuntimeConfigOptions<{
    allowedProjects: number[];
}, object>, {
    ctx: {
        allowedProjects: number[];
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t2
= const initTRPC: TRPCBuilder<object, object>initTRPC
.
TRPCBuilder<object, object>.context<{
    allowedProjects: number[];
}>(): TRPCBuilder<{
    allowedProjects: number[];
}, object>
context
<{
allowedProjects: number[]allowedProjects: number[]; }>() .
TRPCBuilder<{ allowedProjects: number[]; }, object>.create<RuntimeConfigOptions<{
    allowedProjects: number[];
}, object>>(opts?: RuntimeConfigOptions<{
    allowedProjects: number[];
}, object> | undefined): TRPCRootObject<{
    allowedProjects: number[];
}, object, RuntimeConfigOptions<{
    allowedProjects: number[];
}, object>, {
    ctx: {
        allowedProjects: number[];
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
create
();
const
const accessControlledProcedure3: ProcedureBuilder<{
    allowedProjects: number[];
}, object, {}, {
    projectId: string;
}, {
    projectId: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
accessControlledProcedure3
=
const t2: TRPCRootObject<{
    allowedProjects: number[];
}, object, RuntimeConfigOptions<{
    allowedProjects: number[];
}, object>, {
    ctx: {
        allowedProjects: number[];
    };
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t2
.
TRPCRootObject<{ allowedProjects: number[]; }, object, RuntimeConfigOptions<{ allowedProjects: number[]; }, object>, { ctx: { allowedProjects: number[]; }; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<{
    allowedProjects: number[];
}, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
procedure
.
ProcedureBuilder<{ allowedProjects: number[]; }, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.input<z.ZodObject<{
    projectId: z.ZodString;
}, z.core.$strip>>(schema: z.ZodObject<{
    projectId: z.ZodString;
}, z.core.$strip>): ProcedureBuilder<{
    allowedProjects: number[];
}, object, object, {
    projectId: string;
}, {
    projectId: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
input
(import zz.
function object<{
    projectId: z.ZodString;
}>(shape?: {
    projectId: z.ZodString;
} | undefined, params?: string | {
    error?: string | z.core.$ZodErrorMap<NonNullable<z.core.$ZodIssueUnrecognizedKeys | z.core.$ZodIssueInvalidType<unknown>>> | undefined;
    message?: string | undefined | undefined;
} | undefined): z.ZodObject<{
    projectId: z.ZodString;
}, z.core.$strip>
object
({ projectId: z.ZodStringprojectId: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() }))
.
ProcedureBuilder<{ allowedProjects: number[]; }, object, object, { projectId: string; }, { projectId: string; }, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<object>(fn: MiddlewareBuilder<{
    allowedProjects: number[];
}, object, object, {
    projectId: string;
}> | MiddlewareFunction<{
    allowedProjects: number[];
}, object, object, object, {
    projectId: string;
}>): ProcedureBuilder<{
    allowedProjects: number[];
}, object, {}, {
    projectId: string;
}, {
    projectId: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use
(projectAccessMiddleware);
Argument of type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: string; }>' is not assignable to parameter of type 'MiddlewareBuilder<{ allowedProjects: number[]; }, object, object, { projectId: string; }> | MiddlewareFunction<{ allowedProjects: number[]; }, object, object, object, { projectId: string; }>'. Type 'MiddlewareBuilder<{ allowedProjects: string[]; }, object, object, { projectId: string; }>' is not assignable to type 'MiddlewareBuilder<{ allowedProjects: number[]; }, object, object, { projectId: string; }>'. Types of property 'unstable_pipe' are incompatible. Type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ allowedProjects: string[]; }, object, object, $ContextOverridesOut, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, $ContextOverridesOut, { ...; }>) => MiddlewareBuilder<...>' is not assignable to type '<$ContextOverridesOut>(fn: MiddlewareFunction<{ allowedProjects: number[]; }, object, object, $ContextOverridesOut, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: number[]; }, object, $ContextOverridesOut, { ...; }>) => MiddlewareBuilder<...>'. Types of parameters 'fn' and 'fn' are incompatible. Type 'MiddlewareFunction<{ allowedProjects: number[]; }, object, object, any, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: number[]; }, object, any, { projectId: string; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, any, { projectId: string; }>'. Type 'MiddlewareFunction<{ allowedProjects: number[]; }, object, object, any, { projectId: string; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }> | MiddlewareBuilder<{ allowedProjects: string[]; }, object, any, { projectId: string; }>'. Type 'MiddlewareFunction<{ allowedProjects: number[]; }, object, object, any, { projectId: string; }>' is not assignable to type 'MiddlewareFunction<{ allowedProjects: string[]; }, object, object, any, { projectId: string; }>'. Types of parameters 'opts' and 'opts' are incompatible. Type '{ ctx: { allowedProjects: string[]; }; type: "query" | "mutation" | "subscription"; path: string; input: { projectId: string; }; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; batchIndex: number; next: { ...; }; }' is not assignable to type '{ ctx: { allowedProjects: number[]; }; type: "query" | "mutation" | "subscription"; path: string; input: { projectId: string; }; getRawInput: GetRawInputFn; meta: object | undefined; signal: AbortSignal | undefined; batchIndex: number; next: { ...; }; }'. The types of 'ctx.allowedProjects' are incompatible between these types. Type 'string[]' is not assignable to type 'number[]'. Type 'string' is not assignable to type 'number'.

Here is an example with multiple standalone middlewares:

import { 
const experimental_standaloneMiddleware: <TCtx extends {
    ctx?: object;
    meta?: object;
    input?: unknown;
}>() => {
    create: <$ContextOverrides>(fn: MiddlewareFunction<TCtx extends {
        ctx: infer T extends object;
    } ? T : any, TCtx extends {
        meta: infer T_1 extends object;
    } ? T_1 : object, object, $ContextOverrides, TCtx extends {
        input: infer T_2;
    } ? T_2 : unknown>) => MiddlewareBuilder<TCtx extends {
        ctx: infer T extends object;
    } ? T : any, TCtx extends {
        meta: infer T_1 extends object;
    } ? T_1 : object, $ContextOverrides, TCtx extends {
        input: infer T_2;
    } ? T_2 : unknown>;
}
experimental_standaloneMiddleware
, const initTRPC: TRPCBuilder<object, object>initTRPC } from '@trpc/server';
import * as import zz from 'zod'; const
const t: TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
= const initTRPC: TRPCBuilder<object, object>initTRPC.
TRPCBuilder<object, object>.create<RuntimeConfigOptions<object, object>>(opts?: RuntimeConfigOptions<object, object> | undefined): TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
create
();
const
const schemaA: z.ZodObject<{
    valueA: z.ZodString;
}, z.core.$strip>
schemaA
= import zz.
function object<{
    valueA: z.ZodString;
}>(shape?: {
    valueA: 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<{
    valueA: z.ZodString;
}, z.core.$strip>
object
({ valueA: z.ZodStringvalueA: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() });
const
const schemaB: z.ZodObject<{
    valueB: z.ZodString;
}, z.core.$strip>
schemaB
= import zz.
function object<{
    valueB: z.ZodString;
}>(shape?: {
    valueB: 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<{
    valueB: z.ZodString;
}, z.core.$strip>
object
({ valueB: z.ZodStringvalueB: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string() });
const
const valueAUppercaserMiddleware: MiddlewareBuilder<any, object, {
    valueAUppercase: string;
}, {
    valueA: string;
}>
valueAUppercaserMiddleware
=
experimental_standaloneMiddleware<{
    input: z.infer<typeof schemaA>;
}>(): {
    create: <$ContextOverrides>(fn: MiddlewareFunction<any, object, object, $ContextOverrides, {
        valueA: string;
    }>) => MiddlewareBuilder<any, object, $ContextOverrides, {
        valueA: string;
    }>;
}
experimental_standaloneMiddleware
<{
input: {
    valueA: string;
}
input
: import zz.
type infer<T> = T extends {
    _zod: {
        output: any;
    };
} ? T["_zod"]["output"] : unknown
export infer
infer
<typeof
const schemaA: z.ZodObject<{
    valueA: z.ZodString;
}, z.core.$strip>
schemaA
>;
}>().
create: <{
    valueAUppercase: string;
}>(fn: MiddlewareFunction<any, object, object, {
    valueAUppercase: string;
}, {
    valueA: string;
}>) => MiddlewareBuilder<any, object, {
    valueAUppercase: string;
}, {
    valueA: string;
}>
create
((
opts: {
    ctx: object | {};
    type: ProcedureType;
    path: string;
    input: {
        valueA: string;
    };
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
) => {
return
opts: {
    ctx: object | {};
    type: ProcedureType;
    path: string;
    input: {
        valueA: string;
    };
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
.
next: <{
    valueAUppercase: string;
}>(opts: {
    ctx?: {
        valueAUppercase: string;
    } | undefined;
    input?: unknown;
}) => Promise<MiddlewareResult<{
    valueAUppercase: string;
}>> (+2 overloads)
next
({
ctx?: {
    valueAUppercase: string;
} | undefined
ctx
: { valueAUppercase: stringvalueAUppercase:
opts: {
    ctx: object | {};
    type: ProcedureType;
    path: string;
    input: {
        valueA: string;
    };
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
.
input: {
    valueA: string;
}
input
.valueA: stringvalueA.String.toUpperCase(): stringtoUpperCase() },
}); }); const
const valueBUppercaserMiddleware: MiddlewareBuilder<any, object, {
    valueBUppercase: string;
}, {
    valueB: string;
}>
valueBUppercaserMiddleware
=
experimental_standaloneMiddleware<{
    input: z.infer<typeof schemaB>;
}>(): {
    create: <$ContextOverrides>(fn: MiddlewareFunction<any, object, object, $ContextOverrides, {
        valueB: string;
    }>) => MiddlewareBuilder<any, object, $ContextOverrides, {
        valueB: string;
    }>;
}
experimental_standaloneMiddleware
<{
input: {
    valueB: string;
}
input
: import zz.
type infer<T> = T extends {
    _zod: {
        output: any;
    };
} ? T["_zod"]["output"] : unknown
export infer
infer
<typeof
const schemaB: z.ZodObject<{
    valueB: z.ZodString;
}, z.core.$strip>
schemaB
>;
}>().
create: <{
    valueBUppercase: string;
}>(fn: MiddlewareFunction<any, object, object, {
    valueBUppercase: string;
}, {
    valueB: string;
}>) => MiddlewareBuilder<any, object, {
    valueBUppercase: string;
}, {
    valueB: string;
}>
create
((
opts: {
    ctx: object | {};
    type: ProcedureType;
    path: string;
    input: {
        valueB: string;
    };
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
) => {
return
opts: {
    ctx: object | {};
    type: ProcedureType;
    path: string;
    input: {
        valueB: string;
    };
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
.
next: <{
    valueBUppercase: string;
}>(opts: {
    ctx?: {
        valueBUppercase: string;
    } | undefined;
    input?: unknown;
}) => Promise<MiddlewareResult<{
    valueBUppercase: string;
}>> (+2 overloads)
next
({
ctx?: {
    valueBUppercase: string;
} | undefined
ctx
: { valueBUppercase: stringvalueBUppercase:
opts: {
    ctx: object | {};
    type: ProcedureType;
    path: string;
    input: {
        valueB: string;
    };
    getRawInput: GetRawInputFn;
    meta: object | undefined;
    signal: AbortSignal | undefined;
    batchIndex: number;
    next: {
        (): Promise<MiddlewareResult<object>>;
        <$ContextOverride>(opts: {
            ctx?: $ContextOverride | undefined;
            input?: unknown;
        }): Promise<MiddlewareResult<$ContextOverride>>;
        (opts: {
            getRawInput: GetRawInputFn;
        }): Promise<...>;
    };
}
opts
.
input: {
    valueB: string;
}
input
.valueB: stringvalueB.String.toUpperCase(): stringtoUpperCase() },
}); }); const
const combinedInputThatSatisfiesBothMiddlewares: z.ZodObject<{
    valueA: z.ZodString;
    valueB: z.ZodString;
    extraProp: z.ZodString;
}, z.core.$strip>
combinedInputThatSatisfiesBothMiddlewares
= import zz.
function object<{
    valueA: z.ZodString;
    valueB: z.ZodString;
    extraProp: z.ZodString;
}>(shape?: {
    valueA: z.ZodString;
    valueB: z.ZodString;
    extraProp: 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<{
    valueA: z.ZodString;
    valueB: z.ZodString;
    extraProp: z.ZodString;
}, z.core.$strip>
object
({
valueA: z.ZodStringvalueA: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string(), valueB: z.ZodStringvalueB: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string(), extraProp: z.ZodStringextraProp: import zz.function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload)string(), });
const t: TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, {
    ctx: object;
    meta: object;
    errorShape: DefaultErrorShape;
    transformer: false;
}>
t
.TRPCRootObject<object, object, RuntimeConfigOptions<object, object>, { ctx: object; meta: object; errorShape: DefaultErrorShape; transformer: false; }>.procedure: ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>procedure
.
ProcedureBuilder<object, object, object, UnsetMarker, UnsetMarker, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.input<z.ZodObject<{
    valueA: z.ZodString;
    valueB: z.ZodString;
    extraProp: z.ZodString;
}, z.core.$strip>>(schema: z.ZodObject<{
    valueA: z.ZodString;
    valueB: z.ZodString;
    extraProp: z.ZodString;
}, z.core.$strip>): ProcedureBuilder<object, object, object, {
    valueA: string;
    valueB: string;
    extraProp: string;
}, {
    valueA: string;
    valueB: string;
    extraProp: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
input
(
const combinedInputThatSatisfiesBothMiddlewares: z.ZodObject<{
    valueA: z.ZodString;
    valueB: z.ZodString;
    extraProp: z.ZodString;
}, z.core.$strip>
combinedInputThatSatisfiesBothMiddlewares
)
.
ProcedureBuilder<object, object, object, { valueA: string; valueB: string; extraProp: string; }, { valueA: string; valueB: string; extraProp: string; }, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<{
    valueAUppercase: string;
}>(fn: MiddlewareBuilder<{}, object, {
    valueAUppercase: string;
}, {
    valueA: string;
    valueB: string;
    extraProp: string;
}> | MiddlewareFunction<object, object, object, {
    valueAUppercase: string;
}, {
    valueA: string;
    valueB: string;
    extraProp: string;
}>): ProcedureBuilder<object, object, {
    valueAUppercase: string;
}, {
    valueA: string;
    valueB: string;
    extraProp: string;
}, {
    valueA: string;
    valueB: string;
    extraProp: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use
(
const valueAUppercaserMiddleware: MiddlewareBuilder<any, object, {
    valueAUppercase: string;
}, {
    valueA: string;
}>
valueAUppercaserMiddleware
)
.
ProcedureBuilder<object, object, { valueAUppercase: string; }, { valueA: string; valueB: string; extraProp: string; }, { valueA: string; valueB: string; extraProp: string; }, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.use<{
    valueBUppercase: string;
}>(fn: MiddlewareBuilder<{
    valueAUppercase: string;
}, object, {
    valueBUppercase: string;
}, {
    valueA: string;
    valueB: string;
    extraProp: string;
}> | MiddlewareFunction<object, object, {
    valueAUppercase: string;
}, {
    valueBUppercase: string;
}, {
    valueA: string;
    valueB: string;
    extraProp: string;
}>): ProcedureBuilder<object, object, {
    valueAUppercase: string;
    valueBUppercase: string;
}, {
    valueA: string;
    valueB: string;
    extraProp: string;
}, {
    valueA: string;
    valueB: string;
    extraProp: string;
}, UnsetMarker, UnsetMarker, false, DefaultErrorShape>
use
(
const valueBUppercaserMiddleware: MiddlewareBuilder<any, object, {
    valueBUppercase: string;
}, {
    valueB: string;
}>
valueBUppercaserMiddleware
)
.
ProcedureBuilder<object, object, { valueAUppercase: string; valueBUppercase: string; }, { valueA: string; valueB: string; extraProp: string; }, { valueA: string; valueB: string; extraProp: string; }, UnsetMarker, UnsetMarker, false, DefaultErrorShape>.query<string>(resolver: ProcedureResolver<object, object, {
    valueAUppercase: string;
    valueBUppercase: string;
}, {
    valueA: string;
    valueB: string;
    extraProp: string;
}, UnsetMarker, string>): QueryProcedure<{
    input: {
        valueA: string;
        valueB: string;
        extraProp: string;
    };
    output: string;
    meta: object;
    errorShape: DefaultErrorShape;
}>
query
(
({
ProcedureResolverOptions<object, object, { valueAUppercase: string; valueBUppercase: string; }, { valueA: string; valueB: string; extraProp: string; }>.input: {
    valueA: string;
    valueB: string;
    extraProp: string;
}
input
: { valueA: stringvalueA, valueB: stringvalueB, extraProp: stringextraProp },
ProcedureResolverOptions<object, object, { valueAUppercase: string; valueBUppercase: string; }, { valueA: string; valueB: string; extraProp: string; }>.ctx: {
    valueAUppercase: string;
    valueBUppercase: string;
}
ctx
: { valueAUppercase: stringvalueAUppercase, valueBUppercase: stringvalueBUppercase },
}) => `valueA: ${valueA: stringvalueA}, valueB: ${valueB: stringvalueB}, extraProp: ${extraProp: stringextraProp}, valueAUppercase: ${valueAUppercase: stringvalueAUppercase}, valueBUppercase: ${valueBUppercase: stringvalueBUppercase}`, );