Incorrect schema composition in middleware and server functions
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 15.1k
- Forks
- 1.9k
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 143
Description
Which project does this relate to?
Start
Describe the bug
Consider defining schema validation in middleware and server functions, like such:
import { createFileRoute } from '@tanstack/react-router'
import { createMiddleware, createServerFn } from '@tanstack/react-start';
import { z } from 'zod'
export const Route = createFileRoute('/')({
component: Home,
})
export const fooWare = createMiddleware({
type: 'function',
})
.validator(z.object({ foo: z.string() }))
.server(async ({ next }) => {
return next();
});
export const barWare = createMiddleware({
type: 'function',
})
.validator(z.object({ bar: z.string() }))
.server(async ({ next }) => {
return next();
});
export const bazFunc = createServerFn({
method: 'POST',
response: 'data',
})
.middleware([fooWare, barWare])
.validator(
z.object({
baz: z.string(),
}),
)
.handler(
async ({ data }) => {
console.log({ data })
return { data }
})
function Home() {
return (
<div className="p-2">
<button onClick={async () => {
console.log({
data: await bazFunc({
data: {
foo: 'foo',
bar: 'bar',
baz: 'baz',
}
})
})
}}>
Click me!
</button>
</div>
)
}
When you hover data in the server function, it looks like you can access all properties validated in the middleware as well as the function:
And indeed, if you leave out any properties in the call, you get an error:
However, when you click the button in this example, you get a server-side error:
Server Fn Error!
Error: [
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": [
"bar"
],
"message": "Required"
}
]
And a client-side error as well:
This is because z.object drops unknown properties, and so starting at fooWare, everything other than foo gets dropped.
Workaround
You can work around this by using z.looseObject instead of z.object.
Your Example Website or App
Steps to Reproduce the Bug or Issue
- Start the app
- Click the button
Expected behavior
There are a few ways to solve this:
- Middleware could only use schemas for validation and not for transforming. I don't like this solution, it implies lots of other changes.
- Somehow change the schema inference to use composition. Instead of effectively
z.infer<typeof FooSchema> & z.infer<typeof BarSchema> & z.infer<typeof BazSchema>since this isn't accurate. - Document that folks should be careful with
z.object, but type inference is still broken.
Screenshots or Videos
No response
Platform
- Router / Start Version: 1.131.28
- OS: macOS
- Browser: Firefox
- Browser Version: 142.0
- Bundler: vite
- Bundler Version: 7.1.3
Additional context
No response
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the reproduction at src/routes/index.tsx and run the app, then trace how middleware and server-function validators compose schemas. Compare the inferred foo/bar/baz data with the runtime behavior of z.object dropping unknown properties. Done means the validated request and its TypeScript type agree while missing required fields remain rejected.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, typescript
- Domain
- api, backend, backend-api-design
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 32/100