Skip to content

2.0: Standard Schema validation for query/action input #569

Description

@ryansolid

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:

const getUser = 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.

Refs #507

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions