useApiForm (a form bound to a Zod schema), useApiMutation (the typed mutation), and handleApiError (server errors mapped back onto fields).
useApiForm, useApiMutation and handleApiError ship with the Auth plugin (auth-starter), which adds the full-stack web wiring on top of the base app.useApiForm
useApiForm takes a Zod schema and returns a typed react-hook-form instance with the zodResolver already applied. The form’s values are inferred from the schema — no separate FormParams type to maintain.
useForm options (mode, defaultValues, …) as the second argument; only resolver is owned by the hook.
This repo uses Zod 4. Reach for
z.email(), z.url(), z.uuid() rather than Zod 3’s z.string().email().Submitting
Pair the form withuseApiMutation for a matching endpoint. On success, seed or invalidate the cache; on error, hand the error to handleApiError:
handleApiError
handleApiError(e, setError?) from @/utils reads the structured errors object off an oRPC error:
- Field errors (
{ fullName: 'Full name is required' }) are pushed onto the form viasetError, focusing the first one. - A
globalerror is surfaced as a Sonner toast.
form.setError to route messages onto fields; omit it to only toast.
Complete example
A real route component — a TanStack Router route underapps/web/src/routes/**. No document <head> helpers, no page-scope wrappers; layout and auth are handled by the parent route (_authenticated). The form lives in FormProvider so child inputs can use useFormContext.
.input() schema, the response is typed from its .output(), and validation messages your handler returns under errors land back on the right fields — one schema, checked on both sides. See Calling the API for the client and query keys.