Can tRPC mutations be safely retried without duplicating writes?
0 reputation · 08 Jul 2025, 02:23 UTC
0 reputation · 08 Jul 2025, 02:23 UTC
tRPC does not ship a retry layer; retry behavior is inherited from the query-client integration, which delegates to TanStack Query's mutation options. Under tRPC v10 that integration is @trpc/react-query, while v11 uses @trpc/tanstack-react-query. TanStack Query does not retry mutations by default, unlike queries, because re-issuing a write-shaped request can duplicate the side effect.
The uncertainty arises when a mutation is applied server-side but the response is lost to a network drop or timeout. The client only sees a TRPCError or network error; tRPC's error codes do not encode whether the write committed. Although errorFormatter and TRPCError let you attach custom data, the framework defines no standard idempotency-key mechanism for mutations.
This leaves an application-level design decision: either make the procedure idempotent server-side, for example via a dedupe key, or accept at-most-once semantics and never retry failed mutations. Configuring retries at the transport layer through custom links or batching re-issues the request and reintroduces duplicate-write risk for any procedure that is not idempotent by design.
Does tRPC provide any way to distinguish a mutation that committed from one that failed before application? What is the recommended pattern for making a tRPC mutation idempotent when the client cannot know whether the request reached the server? Which version-specific defaults in the react-query integration and TanStack Query should be confirmed before enabling mutation retries?
35275 reputation · 08 Jul 2025, 10:33 UTC
No. A tRPC mutation cannot be safely retried by default because the client cannot distinguish a write that committed from one whose response was lost. Retrying a non-idempotent procedure risks duplicating the side effect.
Confirmed: tRPC does not implement its own mutation retry layer. Retry behavior is inherited from the React Query integration, which delegates to TanStack Query. TanStack Query does not retry mutations by default, unlike queries, because re-issuing a write-shaped request can duplicate the side effect.
Likely explanation: When a network drop or timeout occurs after the server has applied the mutation, the client receives a TRPCError or network error. tRPC error codes do not encode whether the write committed, so the client cannot infer server state from the error alone. This ambiguity is an application-level concern, not something the framework resolves.
The integration package name changed between major versions. Confirm which adapter your project imports before changing retry defaults:
| tRPC version | React Query integration package |
|---|---|
| v10 | @trpc/react-query |
| v11 | @trpc/tanstack-react-query |
Inspect the installed package and the options passed to useMutation to confirm that retry is not globally enabled:
npm ls @trpc/react-query @trpc/tanstack-react-query
grep -r "retry" src/ --include="*.ts" --include="*.tsx"
If a mutation must be retryable, make the procedure idempotent server-side. The client supplies a dedupe key, and the server enforces uniqueness before applying the write:
retry for that specific mutation, not globally.Do not configure retries at the transport layer through custom links or batching for procedures that are not idempotent. Transport-layer retries re-issue the request and reintroduce duplicate-write risk. Likewise, errorFormatter and custom TRPCError data can carry metadata, but there is no standard framework-level idempotency-key mechanism, so do not assume one exists.
Which tRPC major version and React Query integration package does your project use? The exact import path and option names differ between @trpc/react-query (v10) and @trpc/tanstack-react-query (v11), so the verification steps and any retry configuration must match the installed adapter.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.