tRPC v11
The decisions tRPC forces and how to settle them, written so an agent applies them while writing or reviewing code. Every rule names the wrong default it corrects; there is no rule for what the model already gets right.
Pinned to tRPC v11, verified against 11.18.0 (2026-06-17). There is no v12 — but several APIs here carry @deprecated … will be removed in v12 and still ship today. Peers: typescript >=5.7.2, @tanstack/react-query ^5.80.3, react >=18.2.0.
When to Apply
Use this skill when:
- Code imports
initTRPC,createTRPCContext,createTRPCOptionsProxy,createTRPCReact,createTRPCClient,httpBatchLink,httpSubscriptionLink,splitLink,fetchRequestHandler,createCallerFactory, orTRPCError— or defines procedures with.input()/.output()/.use()/.query()/.mutation()/.subscription() - The user says "everything types as
any", "invalidation isn't working", "contextMap[utilName] is not a function", "the transformer property moved", "414 / 413 on the dashboard", "subscriptions won't connect", "the prefetch isn't hydrating", or "it 404s every procedure" - Migrating a codebase from tRPC v10, or reviewing tRPC code written against v10-era examples — the highest-yield moment, because the recommended React client changed and stale code often still compiles
- Adding the first subscription, the first file upload, or the first RSC prefetch to an existing router — each crosses into a surface where v11 replaced the v10 approach outright
- Reviewing a batched client as the app grows, where the uncapped defaults turn into intermittent production failures
This skill is NOT for:
- General TanStack Query semantics — cache lifetimes, retry,
useMutationergonomics (→tanstack-query) - Authoring or migrating the Zod schemas used as validators (→
zod) - Next.js App Router routing, streaming, or caching beyond the tRPC handler (→
nextjs) - General TypeScript narrowing and generics (→
typescript)
Rule Categories
| # | Category | Prefix | Covers |
|---|---|---|---|
| 1 | React Client Surface | client- | Which integration, options factories, query keys, invalidation, subscribing |
| 2 | v10 → v11 Drift | mig- | Renamed, removed, and lazily-materialized APIs; peer-version floors |
| 3 | Router & Procedure Construction | proc- | Instance identity, builder order, context narrowing, input merging |
| 4 | Error & Validation Semantics | err- | What surfaces, what leaks, which error class |
| 5 | Links, Transport & Serialization | link- | Link chain, batching limits, transformers, adapters, caching |
| 6 | SSR, RSC & Server-Side Calls | rsc- | Prefetch and hydration, QueryClient lifetime, caller misuse |
| 7 | Subscriptions & Streaming | sub- | Async generators over SSE, auth, keepalive, backlog races |
Quick Reference
1. React Client Surface
client-tanstack-integration— Build React data fetching on@trpc/tanstack-react-query;createTRPCReactis now the classic pathclient-invalidate-with-query-filters—useUtils()does not exist on the new proxy; invalidate viaqueryClient+pathFilter()/queryFilter()client-derive-query-keys—getQueryKey()is not on the new proxy; keys come off it asqueryKey()/pathKey()/infiniteQueryKey()client-subscription-options—.useSubscription()is not on the new proxy; feedsubscriptionOptions()touseSubscriptionfrom@trpc/tanstack-react-query
2. v10 → v11 Drift
mig-formdata-is-native— All sixexperimental_*upload APIs were removed; FormData is a native input behind asplitLinkmig-renamed-type-exports—AnyRouter→AnyTRPCRouterand friends;inferRouterInputs/Outputsdid not changemig-await-get-raw-input—rawInputisundefined; inputs are lazy, soawait getRawInput()mig-typescript-version-floor— TS>=5.7.2, and an editor on a different TS silently types everything asany
3. Router & Procedure Construction
proc-narrow-ctx-in-middleware— Barenext()leavesctx.usernullable, so the guard gets cast away with!proc-input-before-use—.use()before.input()runs middleware against unvalidated inputproc-single-trpc-instance— OneinitTRPC.create()per app; mismatched instances makemergeRoutersthrow at runtimeproc-merge-only-object-inputs— Only object schemas merge; anything else silently replaces the earlier parserproc-concat-over-standalone-middleware—experimental_standaloneMiddlewareis deprecated and cannot declare.input(); use.concat()
4. Error & Validation Semantics
err-format-standard-schema-issues— Parser dispatch is ordered, so a Zod-onlyerrorFormatterreturnsnullfor Valibot / Effect (StandardSchemaV1Error) and for ArkType (ArkErrors)err-output-strips-fields—.output()returns the parsed value, so it strips unknown keys; its failure is a 500, not a 4xxerr-nullish-cursor—.optional()cursors 400 only after an invalidate; use.nullish()err-set-isdev-explicitly— Without an explicitisDev, edge runtimes shiperror.data.stackto every client
5. Links, Transport & Serialization
link-transformer-on-terminating-links— The transformer moved into the link; deleting it to clear the branded type error breaksDateat runtimelink-route-subscriptions-separately—httpBatchLinkrejects subscription operations outrightlink-cap-batch-size—maxItemsandmaxURLLengthdefault toInfinity; pair them with the server'smaxBatchSizelink-gate-cache-headers— Batching plus blanketresponseMetacaching lets a CDN serve one user's data to anotherlink-default-to-httpbatchlink—httpBatchStreamLinkcannot set response headers once streaming; it is not the defaultlink-read-batch-headers-from-oplist— Batch links pass{ opList }, not{ op }; destructuring wrong makes every request 401link-scope-body-parsers— A globalexpress.json()drains the stream before tRPC reads itlink-endpoint-matches-mount-path—endpointis the prefix tRPC strips; a mismatch 404s every procedure
6. SSR, RSC & Server-Side Calls
rsc-query-client-per-request— A module-scopeQueryClientserves one user's cache to the next requestrsc-dehydrate-pending-queries— Without the pending override,void prefetchQuerynever reaches the clientrsc-prefetch-through-options-proxy— Caller results never enter the cache, so the client fetches it all againrsc-share-logic-not-callers— A nested caller re-creates context and re-runs the whole middleware chain
7. Subscriptions & Streaming
sub-write-async-generators—observable()is deprecated for removal, and only generators gettracked()reconnect-and-resumesub-keep-credentials-out-of-urls—connectionParamsserializes tokens into the query string, and into every log that sees itsub-enable-sse-keepalive—ping.enabledisfalseby default, so idle SSE streams get reaped by proxies and read as a flaky networksub-attach-listener-before-backlog— Fetching history before attaching the listener drops events only under production load
How to Use
Read a reference file when its decision comes up. Each rule names the wrong default it corrects, then shows the canonical way — with an incorrect/correct contrast only where the wrong way is a real trap.
Two shortcuts worth taking first:
-
Reviewing or migrating existing tRPC code? Start with
client-andmig-. Stale code frequently still compiles, so nothing points you at these; they are what everything else inherits. -
Hardening something already working? The security-shaped rules are
link-gate-cache-headers,rsc-query-client-per-request,err-set-isdev-explicitly,proc-input-before-use, andsub-keep-credentials-out-of-urls. -
Section definitions — category structure and ordering rationale
-
Rule template — for adding new rules
-
AGENTS.md — auto-built table of contents across all rules
Related Skills
tanstack-query— The query layer tRPC's React client now delegates to; owns cache semantics this skill assumeszod— Validator authoring and Zod 4 drift, including the.flatten()→z.treeifyError()change this skill only points attypescript— General type-level work beyond tRPC's inference surface