Skip to content

feat: zod v4 support #768 #776

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from

Conversation

alexcraviotto
Copy link

@alexcraviotto alexcraviotto commented May 31, 2025

Support for Zod v4 with zodResolverV4

This PR adds support for the new version of Zod (v4) by introducing a new resolver: zodResolverV4. This allows users to work with schemas written using the latest version of Zod while maintaining compatibility with older implementations.

To ensure backward compatibility, the original zodResolver has not been removed or modified.

Due to issues related to tests in typeschema.ts, I’ve temporarily installed two separate versions of Zod (zod and zod-v4) to avoid breaking existing functionality. Ideally, in the future, it would be great to update those parts to also use the latest version of Zod.

Usage

Using the new resolver is simple. You just need to import and use zodResolverV4 like so:

const { control, handleSubmit, formState: { errors } } = useForm<User>({
  resolver: zodResolverV4(UserSchema),
  defaultValues: {
    name: '',
    device: {
      platform: 'ios',
      versions: []
    }
  }
});

Big thanks to @hukpo for mentioning this solution in Issue #768 🙌

@bluebill1049 bluebill1049 requested review from jorisre and Copilot June 1, 2025 00:17
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Adds support for Zod v4 by introducing a new zodResolverV4 and preserving the existing resolver.

  • Introduces zodv4.ts with a resolver that uses a Zod v4 alias
  • Updates index.ts to export both zodResolver and zodResolverV4
  • Adjusts package.json to install an aliased Zod v4 and bumps the existing Zod version
  • Cleans up duplicate imports in typebox tests

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

File Description
zod/src/zodv4.ts Implements zodResolverV4 using the aliased Zod
zod/src/index.ts Exports original resolver and adds the v4 alias
typebox/src/tests/typebox.ts Removes duplicate imports in tests
package.json Bumps Zod dev dep and adds zod-v4 alias

zod/src/zodv4.ts Outdated
ResolverSuccess,
appendErrors,
} from 'react-hook-form';
import { ZodError, z } from 'zod-v4/v4';
Copy link
Preview

Copilot AI Jun 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import path 'zod-v4/v4' is unusual and may not resolve correctly. Consider importing directly from the aliased package (e.g. import { ZodError, z } from 'zod-v4';).

Suggested change
import { ZodError, z } from 'zod-v4/v4';
import { ZodError, z } from 'zod-v4';

Copilot uses AI. Check for mistakes.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It needs to be done that way to work.

@@ -1 +1,2 @@
export * from './zod';
export { zodResolver } from './zod';
Copy link
Preview

Copilot AI Jun 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replacing export * with named exports removes other exports from ./zod. If consumers rely on additional types or utilities, re-export them too or restore export *.

Copilot uses AI. Check for mistakes.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do it this way to be able to export both resolvers of both versions.

package.json Outdated
},
"peerDependencies": {
"react-hook-form": "^7.55.0"
},
"dependencies": {
"@standard-schema/utils": "^0.3.0"
"@standard-schema/utils": "^0.3.0",
"zod-v4": "npm:zod@^3.25.0"
Copy link
Preview

Copilot AI Jun 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alias zod-v4 points to zod@^3.25.0, which is still v3. To install Zod v4, update this alias to a ^4.x version.

Suggested change
"zod-v4": "npm:zod@^3.25.0"
"zod-v4": "npm:zod@^4.x"

Copilot uses AI. Check for mistakes.

package.json Outdated
},
"peerDependencies": {
"react-hook-form": "^7.55.0"
},
"dependencies": {
"@standard-schema/utils": "^0.3.0"
"@standard-schema/utils": "^0.3.0",
"zod-v4": "npm:zod@^3.25.0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a dev dependency, like all the other schema libraries

/**
* Creates a resolver function for react-hook-form that validates form data using a Zod schema
* @param {z.ZodSchema<Input>} schema - The Zod schema used to validate the form data
* @param {Partial<z.ParseParams>} [schemaOptions] - Optional configuration options for Zod parsing
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update

Suggested change
* @param {Partial<z.ParseParams>} [schemaOptions] - Optional configuration options for Zod parsing
* @param {z.core.ParseContext<z.core.$ZodIssue>} [schemaOptions] - Optional configuration options for Zod parsing

zod/src/zodv4.ts Outdated
Comment on lines 68 to 84
export function zodResolver<Input extends FieldValues, Context, Output>(
schema: z.ZodSchema<Output, Input>,
schemaOptions?: Partial<z.core.ParseContext<z.core.$ZodIssue>>,
resolverOptions?: {
mode?: 'async' | 'sync';
raw?: false;
},
): Resolver<Input, Context, Output>;

export function zodResolver<Input extends FieldValues, Context, Output>(
schema: z.ZodSchema<Output, Input>,
schemaOptions: Partial<z.core.ParseContext<z.core.$ZodIssue>> | undefined,
resolverOptions: {
mode?: 'async' | 'sync';
raw: true;
},
): Resolver<Input, Context, Input>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these split into separate overloads? Odd considering their return types are identical.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the reason is that

  • in the raw: true case, the output type is Resolver<Input, Context, Input>—the original input is returned
  • whereas in the opposite case (resolverOptions.raw is omitted or false) then the output type is Resolver<Input, Context, Output>.

I suggested the overload in #753 (comment), because I believe without it it’s not possible to express this kind of “conditional return type” 

zod/src/zodv4.ts Outdated
ResolverSuccess,
appendErrors,
} from 'react-hook-form';
import { ZodError, z } from 'zod-v4/v4';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tooling like this should only be importing from "/v4/core". This way you can support Zod (Regular) and Zod Mini schemas simultaneously. See https://zod.dev/library-authors

zod/src/zodv4.ts Outdated
): Resolver<Input, Context, Output>;

export function zodResolver<Input extends FieldValues, Context, Output>(
schema: z.ZodSchema<Output, Input>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be z.core.$ZodType to allow for Zod Mini schemas

zod/src/zodv4.ts Outdated
return async (values: Input, _, options) => {
try {
const data = await schema[
resolverOptions.mode === 'sync' ? 'parse' : 'parseAsync'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After switching over to z.core.$ZodType, you'll need to use the top level z.parse and z.parseAsync functions here. The $ZodType base class has no methods.

@alexcraviotto
Copy link
Author

Thanks for the review @colinhacks 🙌

I've uploaded the changes in the last commit.

@bluebill1049
Copy link
Member

thanks @colinhacks for the review 🙏

@@ -5,6 +5,7 @@
"name": "@hookform/resolvers",
"dependencies": {
"@standard-schema/utils": "^0.3.0",
"zod-v4": "npm:zod@^3.25.0",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lock file also needs to be updated after moving zod-v4 to devDependencies

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lock file also needs to be updated after moving zod-v4 to devDependencies

Shouldn't zod-v4 be under devDependencies? Only the standard schema is included as a dependency.

ResolverSuccess,
appendErrors,
} from 'react-hook-form';
import { ZodError } from 'zod-v4/v4';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for v4-mini compatibility, the code should use core.$ZodError instead.

https://zod.dev/packages/core?id=errors#errors

Comment on lines +69 to +76
export function zodResolver<Input extends FieldValues, Context, Output>(
schema: core.$ZodType<Output, Input>,
schemaOptions?: Partial<core.ParseContext<core.$ZodIssue>>,
resolverOptions?: {
mode?: 'async' | 'sync';
raw?: false;
},
): Resolver<Input, Context, Output>;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems like the overload for raw: true was incorrectly removed, which breaks the types when this option is passed @alexcraviotto

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -54,7 +55,7 @@
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.0.9",
"yup": "^1.6.1",
"zod": "^3.24.2",
"zod": "3.24.4",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixing @CHC383 's suggestion by moving zod-v4 to devDependencies

Suggested change
"zod": "3.24.4",
"zod": "3.24.4",
"zod-v4": "npm:zod@^3.25.0",

@jorisre
Copy link
Member

jorisre commented Jun 7, 2025

Thanks a lot @alexcraviotto for your contribution. Closing in favor of #777

@jorisre jorisre closed this Jun 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants