You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
2.0: Standard Schema validation for query/action input #569
Tracking the actionable part of #507 as a 2.0 improvement.
Problem
"use server" functions type their arguments as written (e.g. email: string), but the input actually crosses a serialization boundary and is attacker-controlled. Typing it honestly as unknown destroys caller-side DX. Users want "typed on the outside, validated on the inside."
Userland wrappers can't solve this: composing a validator around a "use server" function runs into server-function compilation constraints (Server Functions cannot be nested in other blocks or functions - see #507 (comment)), so first-class router support is the only clean path.
Proposal
Accept an optional Standard Schema validator in query and action:
constgetUser=query(z.string().email(),async(email)=>{"use server";// email: string, already validated at the boundary},"get-user");
Caller-side argument types derive from the schema's input type.
The function body receives the schema's output type, validated server-side before the body runs.
Standard Schema keeps the router decoupled from any particular validation library (Zod, Valibot, ArkType all implement it), same approach TanStack Router took.
Open design questions:
Overload shape (query(schema, fn, name) vs options object) and how it interacts with multi-argument functions.
Validation failure behavior (throw a typed error? 400-style response for actions?).
Whether validation also runs on direct server-side calls or only across the RPC boundary.
they actually can with the old bling server$() design. in our codebase at work I have a custom babel plugin that converts server$() to "use server" letting me write
constqueryFoo=query(server$(schemaInput(v.string(),input=>{// I can trust `input` is a string})))
where schemaInput() is a userland input validator.
we actually have this pattern for other things too. like I have a bouncer() function that bails out if the user is not logged in or otherwise provides their user record from the db to the server function.
I'd love to make the patterns I use at work open source but it requires patching (now forking) vinxi to add a custom hook to let me add the babel plugin. and there's a whole devinxi thing going on right now and solid and start 2.0. so I've just been waiting
I meant a runtime wrapper. Compiling something can work. But don't have much interest in doing so for this. Fine if people do that in user space I suppose, but the use of directives is intentional. We did see some of this happen in user space earlier with prpc$ but honestly the fact that this stuff isn't truly composable/transferable is one of the reasons I don't like having a function wrapper as the API surface. We went this direction with Bling but then took some learning from it.
Tanner stayed on the compiled function form because emphasis on TS but from a structural standpoint I don't think it is really debatable.
schemaInput()is a runtime wrapper. it's literally just a normal js functions that takes 2 arguments and returns a function. the only thing being compiled is server$() but that's just to get the old design back and "use server" is compiled too anyway. the old bling design was composable. that's fine you don't like wrappers as an API . this is a normal JS problem that people have been trying to fix since they started working on decorator functions though that's been taking a while to get through specs.
special casing query() with a schema in the compiler doesn't seem composable to me. it would need to be more generic I think in case I want to do things other than input validation
Closing as not planned. In 2.0, input validation for server functions lives on the server side of the function, so the router doesn't need to run validators in the client for query/action input. Standard Schema validation stays available for search params.
Activity
samualtnorman commentedon Jul 15, 2026
they actually can with the old bling
server$()design. in our codebase at work I have a custom babel plugin that convertsserver$()to"use server"letting me writewhere
schemaInput()is a userland input validator.we actually have this pattern for other things too. like I have a
bouncer()function that bails out if the user is not logged in or otherwise provides their user record from the db to the server function.I'd love to make the patterns I use at work open source but it requires patching (now forking) vinxi to add a custom hook to let me add the babel plugin. and there's a whole devinxi thing going on right now and solid and start 2.0. so I've just been waiting
ryansolid commentedon Jul 15, 2026
I meant a runtime wrapper. Compiling something can work. But don't have much interest in doing so for this. Fine if people do that in user space I suppose, but the use of directives is intentional. We did see some of this happen in user space earlier with prpc$ but honestly the fact that this stuff isn't truly composable/transferable is one of the reasons I don't like having a function wrapper as the API surface. We went this direction with Bling but then took some learning from it.
Tanner stayed on the compiled function form because emphasis on TS but from a structural standpoint I don't think it is really debatable.
samualtnorman commentedon Jul 15, 2026
schemaInput()is a runtime wrapper. it's literally just a normal js functions that takes 2 arguments and returns a function. the only thing being compiled isserver$()but that's just to get the old design back and"use server"is compiled too anyway. the old bling design was composable. that's fine you don't like wrappers as an API . this is a normal JS problem that people have been trying to fix since they started working on decorator functions though that's been taking a while to get through specs.special casing
query()with a schema in the compiler doesn't seem composable to me. it would need to be more generic I think in case I want to do things other than input validationryansolid commentedon Oct 14, 2026
Generated by Claude Code