Problem
We use function middleware to catch errors and convert them to structured error responses so the client can branch on error codes (show field-level validation errors, redirect on auth failures, show billing-specific messages, etc.).
export const errorHandler = createMiddleware({ type: 'function' }).server(async ({ next }) => {
try {
return await next()
} catch (error) {
if (error instanceof DomainError) {
return { success: false, error: { code: 'BILLING_ERROR', message: error.message } }
}
return { success: false, error: { code: 'INTERNAL_SERVER_ERROR', message: 'Something went wrong' } }
}
})
Registered globally:
createStart(() => ({
functionMiddleware: [errorHandler],
}))
Expected: Client receives { success: false, error: { code: 'BILLING_ERROR', ... } } as the resolved value.
Actual: Client promise rejects with just the inner error object { code: 'BILLING_ERROR', ... }. The { success: false } wrapper is stripped.
Root cause
executeMiddleware uses { result, error } internally (ServerFnMiddlewareResult). When middleware returns an object with an .error property, line 140 of createServerFn.ts treats it as a middleware-chain error and throws it:
if (result.error) throw result.error
Any return value from middleware that has a truthy .error field collides with this internal protocol. The middleware's return is never delivered to the client as a resolved value.
What we need
We want a global error boundary for server functions — similar to FastAPI exception handlers or Express error middleware. The goal is:
- Classified errors (auth, billing, validation) — send a structured error code to the client so it can react (redirect, show field errors, show billing prompts)
- Unexpected errors — log server-side for debugging, send a generic "Something went wrong" to the client
- Single boundary — handlers just throw, middleware handles classification
Questions
- Is returning custom values from middleware catch blocks intended to work? The docs only show
return next() or throw from middleware — never custom returns.
- If not, what's the recommended pattern for global error classification in server functions? Should middleware
throw a structured error object and let the client catch the rejection?
- Would TanStack consider documenting an official error-handling middleware pattern?
Environment
@tanstack/react-start v1.167.17
@tanstack/start-client-core v1.167.17
Problem
We use function middleware to catch errors and convert them to structured error responses so the client can branch on error codes (show field-level validation errors, redirect on auth failures, show billing-specific messages, etc.).
Registered globally:
Expected: Client receives
{ success: false, error: { code: 'BILLING_ERROR', ... } }as the resolved value.Actual: Client promise rejects with just the inner
errorobject{ code: 'BILLING_ERROR', ... }. The{ success: false }wrapper is stripped.Root cause
executeMiddlewareuses{ result, error }internally (ServerFnMiddlewareResult). When middleware returns an object with an.errorproperty, line 140 ofcreateServerFn.tstreats it as a middleware-chain error and throws it:Any return value from middleware that has a truthy
.errorfield collides with this internal protocol. The middleware's return is never delivered to the client as a resolved value.What we need
We want a global error boundary for server functions — similar to FastAPI exception handlers or Express error middleware. The goal is:
Questions
return next()orthrowfrom middleware — never custom returns.throwa structured error object and let the client catch the rejection?Environment
@tanstack/react-startv1.167.17@tanstack/start-client-corev1.167.17