diff --git a/.changeset/form-errors-on-server.md b/.changeset/form-errors-on-server.md
new file mode 100644
index 0000000000..ac4dbf73c3
--- /dev/null
+++ b/.changeset/form-errors-on-server.md
@@ -0,0 +1,5 @@
+---
+'@tanstack/form-core': patch
+---
+
+Infer form-level `errors` from `onServerValidate` specified in `formOptions`.
diff --git a/docs/framework/react/guides/ssr.md b/docs/framework/react/guides/ssr.md
index 9013cb873d..bd711e5215 100644
--- a/docs/framework/react/guides/ssr.md
+++ b/docs/framework/react/guides/ssr.md
@@ -200,6 +200,11 @@ export const formOpts = formOptions({
firstName: '',
age: 0,
},
+ onServerValidate: ({ value }) => {
+ if (value.age < 12) {
+ return 'Server validation: You must be at least 12 to sign up'
+ }
+ },
})
```
@@ -219,11 +224,6 @@ import { formOpts } from './shared-code'
// Create the server action that will infer the types of the form from `formOpts`
const serverValidate = createServerValidate({
...formOpts,
- onServerValidate: ({ value }) => {
- if (value.age < 12) {
- return 'Server validation: You must be at least 12 to sign up'
- }
- },
})
export default async function someAction(prev: unknown, formData: FormData) {
@@ -278,7 +278,7 @@ export const ClientComp = () => {
return (
{
- if (value.age < 12) {
- return 'Server validation: You must be at least 12 to sign up'
- }
- },
})
export default async function someAction(prev: unknown, formData: FormData) {
diff --git a/examples/react/next-server-actions/src/app/client-component.tsx b/examples/react/next-server-actions/src/app/client-component.tsx
index 2b071ef532..3f9b33c0ff 100644
--- a/examples/react/next-server-actions/src/app/client-component.tsx
+++ b/examples/react/next-server-actions/src/app/client-component.tsx
@@ -27,7 +27,7 @@ export const ClientComp = () => {
return (
{
+ if (value.age < 12) {
+ return 'Server validation: You must be at least 12 to sign up'
+ }
+ },
})
diff --git a/packages/form-core/src/FormApi.ts b/packages/form-core/src/FormApi.ts
index 4400017d30..302c0d11b7 100644
--- a/packages/form-core/src/FormApi.ts
+++ b/packages/form-core/src/FormApi.ts
@@ -510,6 +510,12 @@ export interface FormOptions<
TOnDynamic,
TOnDynamicAsync
>
+ /**
+ * Server-only validator used by `createServerValidate`. Specify it in shared
+ * `formOptions` so the client form infers `state.errors` / `errorMap.onServer`
+ * from the same function. This is not executed in the browser.
+ */
+ onServerValidate?: TOnServer
validationLogic?: ValidationLogicFn
diff --git a/packages/form-core/tests/formOptions.test-d.ts b/packages/form-core/tests/formOptions.test-d.ts
index fd8089549f..46d96348de 100644
--- a/packages/form-core/tests/formOptions.test-d.ts
+++ b/packages/form-core/tests/formOptions.test-d.ts
@@ -319,4 +319,33 @@ describe('formOptions', () => {
('Too short!' | 'I just need an error')[]
>()
})
+
+ it('should infer form error types from onServerValidate in formOptions', () => {
+ const formOpts = formOptions({
+ defaultValues: {
+ firstName: '',
+ age: 0,
+ },
+ onServerValidate: ({ value }) => {
+ expectTypeOf(value.age).toEqualTypeOf()
+ if (value.age < 12) {
+ return 'Server validation: You must be at least 12 to sign up' as const
+ }
+ return undefined
+ },
+ })
+
+ const form = new FormApi(formOpts)
+ expectTypeOf(form.state.errors).toEqualTypeOf<
+ 'Server validation: You must be at least 12 to sign up'[]
+ >()
+ expectTypeOf(form.state.errorMap.onServer).toEqualTypeOf<
+ 'Server validation: You must be at least 12 to sign up' | undefined
+ >()
+
+ const form2 = new FormApi({ ...formOpts })
+ expectTypeOf(form2.state.errors).toEqualTypeOf<
+ 'Server validation: You must be at least 12 to sign up'[]
+ >()
+ })
})
diff --git a/packages/react-form/tests/useForm.test-d.tsx b/packages/react-form/tests/useForm.test-d.tsx
index 2e92cf29ce..4e86c0df76 100644
--- a/packages/react-form/tests/useForm.test-d.tsx
+++ b/packages/react-form/tests/useForm.test-d.tsx
@@ -154,4 +154,26 @@ describe('useForm', () => {
expectTypeOf(form.state.values).toExtend()
})
+
+ it('should infer form error types from onServerValidate in formOptions', () => {
+ const formOpts = formOptions({
+ defaultValues: {
+ firstName: '',
+ age: 0,
+ },
+ onServerValidate: ({ value }) => {
+ expectTypeOf(value.age).toEqualTypeOf()
+ if (value.age < 12) {
+ return 'Server validation: You must be at least 12 to sign up' as const
+ }
+ return undefined
+ },
+ })
+
+ const form = useForm({ ...formOpts })
+
+ expectTypeOf(form.state.errors).toEqualTypeOf<
+ 'Server validation: You must be at least 12 to sign up'[]
+ >()
+ })
})