Skip to main content Home Creators trpc trpc middlewares
middlewares Create and compose tRPC middleware with t.procedure.use(), extend context via opts.next({ ctx }), build reusable middleware with .concat() and .unstable_pipe(), define base procedures like publicProcedure and authedProcedure. Access raw input with getRawInput(). Logging, timing, OTEL tracing patterns.
Jump to install Skills Marketplace Discover and explore AI skills built by the community.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Copy promptShow prompt details A direct command skips the review prompt. Inspect the source before running it.
npx skills add https://github.com/trpc/trpc --skill middlewaresThe command stays on one line. Scroll horizontally to inspect it before copying.
Prefer a local copy? Download the files currently available to SkillsMP.
Download Zip Downloading... More from this repository Create a vanilla tRPC client with createTRPCClient<AppRouter>(), configure link chain with httpBatchLink/httpLink, dynamic headers for auth, transformer on links (not client constructor). Infer types with inferRouterInputs and inferRouterOutputs. AbortController signal support. TRPCClientError typing.
Configure the tRPC client link chain: httpLink, httpBatchLink, httpBatchStreamLink, splitLink, loggerLink, wsLink, createWSClient, httpSubscriptionLink, unstable_localLink, retryLink. Choose the right terminating link. Route subscriptions via splitLink. Build custom links for SOA routing. Link options: url, headers, transformer, maxURLLength, maxItems, connectionParams, EventSource ponyfill.
Configure SuperJSON transformer on both server initTRPC.create({ transformer: superjson }) and every client terminating link (httpBatchLink, httpLink, wsLink, httpSubscriptionLink) to support Date, Map, Set, BigInt over the wire. Transformer must match on both sides. In v11, transformer goes on individual links, not the client constructor.
Related occupations SOC
Based on SOC occupation classification
name middlewares description Create and compose tRPC middleware with t.procedure.use(), extend context via opts.next({ ctx }), build reusable middleware with .concat() and .unstable_pipe(), define base procedures like publicProcedure and authedProcedure. Access raw input with getRawInput(). Logging, timing, OTEL tracing patterns.
type core library trpc library_version 11.16.0 requires ["server-setup"] sources ["trpc/trpc:www/docs/server/middlewares.md","trpc/trpc:www/docs/server/authorization.md","trpc/trpc:packages/server/src/unstable-core-do-not-import/middleware.ts","trpc/trpc:packages/server/src/unstable-core-do-not-import/procedureBuilder.ts"]
tRPC -- Middlewares
Setup
{ initTRPC, } ;
= {
?: { : ; : };
};
t = initTRPC. < >(). ();
router = t. ;
publicProcedure = t. ;
middleware = t. ;
import
TRPCError
from
'@trpc/server'
type
Context
user
id
string
isAdmin
boolean
const
context
Context
create
export
const
router
export
const
procedure
export
const
middleware
Core Patterns
Auth middleware that narrows context type
import { initTRPC, TRPCError } from '@trpc/server' ;
type Context = {
user ?: { id : string ; isAdmin : boolean };
};
const t = initTRPC.context <Context >().create ();
export const publicProcedure = t.procedure ;
export const authedProcedure = t.procedure .use (async (opts) => {
const { ctx } = opts;
if (!ctx.user ) {
throw new TRPCError ({ code : 'UNAUTHORIZED' });
}
return opts.next ({
ctx : {
user : ctx.user ,
},
});
});
export const adminProcedure = t.procedure .use (async (opts) => {
const { ctx } = opts;
if (!ctx.user ?.isAdmin ) {
throw new TRPCError ({ code : 'UNAUTHORIZED' });
}
return opts.next ({
ctx : {
user : ctx.user ,
},
});
});
After the middleware, ctx.user is non-nullable in downstream procedures.
Logging and timing middleware
import { initTRPC } from '@trpc/server' ;
const t = initTRPC.create ();
export const loggedProcedure = t.procedure .use (async (opts) => {
const start = Date .now ();
const result = await opts.next ();
const durationMs = Date .now () - start;
const meta = { path : opts.path , type : opts.type , durationMs };
result.ok
? console .log ('OK request timing:' , meta)
: console .error ('Non-OK request timing' , meta);
return result;
});
Reusable middleware with .concat()
import { initTRPC } from '@trpc/server' ;
export function createMyPlugin ( ) {
const t = initTRPC.context <{}>().meta <{}>().create ();
return {
pluginProc : t.procedure .use ((opts ) => {
return opts.next ({
ctx : {
fromPlugin : 'hello from myPlugin' as const ,
},
});
}),
};
}
import { initTRPC } from '@trpc/server' ;
import { createMyPlugin } from './myPlugin' ;
const t = initTRPC.context <{}>().create ();
const plugin = createMyPlugin ();
export const publicProcedure = t.procedure ;
export const procedureWithPlugin = publicProcedure.concat (plugin.pluginProc );
.concat() merges a partial procedure (from any tRPC instance) into your procedure chain, as long as context and meta types overlap.
Extending middlewares with .unstable_pipe() import { initTRPC } from '@trpc/server' ;
const t = initTRPC.create ();
const fooMiddleware = t.middleware ((opts ) => {
return opts.next ({
ctx : { foo : 'foo' as const },
});
});
const barMiddleware = fooMiddleware.unstable_pipe ((opts ) => {
console .log (opts.ctx .foo );
return opts.next ({
ctx : { bar : 'bar' as const },
});
});
const barProcedure = t.procedure .use (barMiddleware);
Piped middlewares run in order and each receives the context from the previous middleware.
Common Mistakes
[CRITICAL] Forgetting to call and return opts.next() import { initTRPC } from '@trpc/server' ;
const t = initTRPC.create ();
const logMiddleware = t.middleware (async (opts) => {
console .log ('request started' );
});
import { initTRPC } from '@trpc/server' ;
const t = initTRPC.create ();
const logMiddleware = t.middleware (async (opts) => {
console .log ('request started' );
const result = await opts.next ();
console .log ('request ended' );
return result;
});
Middleware must call opts.next() and return its result; forgetting this silently drops the request with an INTERNAL_SERVER_ERROR because no middleware marker is returned.
Source: packages/server/src/unstable-core-do-not-import/procedureBuilder.ts
[HIGH] Extending context with wrong type in opts.next() import { initTRPC } from '@trpc/server' ;
const t = initTRPC.create ();
const middleware = t.middleware (async (opts) => {
return opts.next ({ ctx : 'not-an-object' });
});
import { initTRPC } from '@trpc/server' ;
const t = initTRPC.create ();
async function getUser ( ) {
return { id : '1' , name : 'Katt' };
}
const middleware = t.middleware (async (opts) => {
return opts.next ({ ctx : { user : await getUser () } });
});
Context extension in opts.next({ ctx }) must be an object; passing non-object values or overwriting required keys breaks downstream procedures.
Source: www/docs/server/middlewares.md
See Also
server-setup -- initTRPC, routers, procedures, context
validators -- input/output validation with Zod
error-handling -- TRPCError codes used in auth middleware
auth -- full auth patterns combining middleware + client headers