Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

400 Commits

Repository files navigation

React Hook Form Logo - React hook custom hook for form validation

Performant, flexible and extensible forms with easy to use validation.

npm downloadsnpmnpm

React Hook Form Resolvers

This function allows you to use any external validation library such as Yup, Zod, Joi, Vest, Ajv and many others. The goal is to make sure you can seamlessly integrate whichever validation library you prefer. If you're not using a library, you can always write your own logic to validate your forms.

Install

Install your preferred validation library alongside @hookform/resolvers.

npm install @hookform/resolvers # npm
yarn add @hookform/resolvers # yarn
pnpm install @hookform/resolvers # pnpm
bun install @hookform/resolvers # bun
Resolver Comparison
resolverInfer values
from schema
criteriaMode
AJVfirstError | all
ata-validatorfirstError | all
ArktypefirstError
class-validatorfirstError | all
computed-typesfirstError
EffectfirstError | all
fluentvalidation-tsfirstError
io-tsfirstError
joifirstError | all
NopefirstError
Standard SchemafirstError | all
SuperstructfirstError
typanionfirstError
typeboxfirstError | all
typeschemafirstError | all
valibotfirstError | all
vestfirstError | all
vinefirstError | all
yupfirstError | all
zodfirstError | all

TypeScript

Most of the resolvers can infer the output type from the schema. See comparison table for more details.

useForm<Input,Context,Output>()

Example:

import{useForm}from'react-hook-form';import{zodResolver}from'@hookform/resolvers/zod';import{z}from'zod';// or 'zod/v4'constschema=z.object({id: z.number(),});// Automatically infers the output type from the schemauseForm({resolver: zodResolver(schema),});// Force the output typeuseForm<z.input<typeofschema>,any,z.output<typeofschema>>({resolver: zodResolver(schema),});

Links

Table of Contents

API

type Options = {
mode: 'async' | 'sync',
raw?: boolean
}
resolver(schema: object, schemaOptions?: object, resolverOptions: Options)
typeRequiredDescription
schemaobjectvalidation schema
schemaOptionsobjectvalidation library schema options
resolverOptionsobjectresolver options, async is the default mode

Quickstart

Dead simple Object schema validation.

npm

⚠️ Pass context via useForm({ context }), not via yupResolver's schemaOptions. schemaOptions.context is overridden by the form context, so use the useForm context object instead.

// CorrectuseForm({resolver: yupResolver(schema),context: {foo: true},});// Avoid - schemaOptions.context will be ignored/overriddenyupResolver(schema,{context: {foo: true}});
import{useForm}from'react-hook-form';import{yupResolver}from'@hookform/resolvers/yup';import*asyupfrom'yup';constschema=yup.object().shape({name: yup.string().required(),age: yup.number().required(),}).required();constApp=()=>{const{ register, handleSubmit }=useForm({resolver: yupResolver(schema),});return(<formonSubmit={handleSubmit((d)=>console.log(d))}><input{...register('name')}/><inputtype="number"{...register('age')}/><inputtype="submit"/></form>);};

TypeScript-first schema validation with static type inference

npm

⚠️ Example below uses the valueAsNumber, which requires react-hook-form v6.12.0 (released Nov 28, 2020) or later.

import{useForm}from'react-hook-form';import{zodResolver}from'@hookform/resolvers/zod';import{z}from'zod';// or 'zod/v4'constschema=z.object({name: z.string().min(1,{message: 'Required'}),age: z.number().min(10),});constApp=()=>{const{
register,
handleSubmit,formState: { errors },}=useForm({resolver: zodResolver(schema),});return(<formonSubmit={handleSubmit((d)=>console.log(d))}><input{...register('name')}/>{errors.name?.message&&<p>{errors.name?.message}</p>}<inputtype="number"{...register('age',{valueAsNumber: true})}/>{errors.age?.message&&<p>{errors.age?.message}</p>}<inputtype="submit"/></form>);};

⚠️ If your schema uses .default(...) on a field, that field becomes optional on the schema's input type (z.input) but stays required on its output type (z.output/z.infer) — the default only fills it in after validation. Passing a single generic to useForm<T> pins both to the same type and will conflict with zodResolver, which infers input and output separately. Either omit the generic and let it infer from resolver, or specify all three explicitly:

constschema=z.object({debug_mode: z.boolean().default(true)});useForm<z.input<typeofschema>,unknown,z.output<typeofschema>>({resolver: zodResolver(schema),});

A simple and composable way to validate data in JavaScript (or TypeScript).

npm

import{useForm}from'react-hook-form';import{superstructResolver}from'@hookform/resolvers/superstruct';import{object,string,number}from'superstruct';constschema=object({name: string(),age: number(),});constApp=()=>{const{ register, handleSubmit }=useForm({resolver: superstructResolver(schema),});return(<formonSubmit={handleSubmit((d)=>console.log(d))}><input{...register('name')}/><inputtype="number"{...register('age',{valueAsNumber: true})}/><inputtype="submit"/></form>);};

The most powerful data validation library for JS.

npm

import{useForm}from'react-hook-form';import{joiResolver}from'@hookform/resolvers/joi';importJoifrom'joi';constschema=Joi.object({name: Joi.string().required(),age: Joi.number().required(),});constApp=()=>{const{ register, handleSubmit }=useForm({resolver: joiResolver(schema),});return(<formonSubmit={handleSubmit((d)=>console.log(d))}><input{...register('name')}/><inputtype="number"{...register('age')}/><inputtype="submit"/></form>);};

Vest 🦺 Declarative Validation Testing.

npm

import{useForm}from'react-hook-form';import{vestResolver}from'@hookform/resolvers/vest';import{create,test,enforce}from'vest';constvalidationSuite=create((data={})=>{test('username','Username is required',()=>{enforce(data.username).isNotEmpty();});test('password','Password is required',()=>{enforce(data.password).isNotEmpty();});});constApp=()=>{const{ register, handleSubmit }=useForm({resolver: vestResolver(validationSuite),});return(<formonSubmit={handleSubmit((data)=>console.log(data))}><input{...register('username')}/><inputtype="password"{...register('password')}/><inputtype="submit"/></form>);};

Decorator-based property validation for classes.

npm

⚠️ Remember to add these options to your tsconfig.json!

"strictPropertyInitialization": false,
"experimentalDecorators": true
import{useForm}from'react-hook-form';import{classValidatorResolver}from'@hookform/resolvers/class-validator';import{Length,Min,IsEmail}from'class-validator';classUser{
@Length(2,30)username: string;
@IsEmail()email: string;}constresolver=classValidatorResolver(User);constApp=()=>{const{
register,
handleSubmit,formState: { errors },}=useForm<User>({ resolver });return(<formonSubmit={handleSubmit((data)=>console.log(data))}><inputtype="text"{...register('username')}/>{errors.username&&<span>{errors.username.message}</span>}<inputtype="text"{...register('email')}/>{errors.email&&<span>{errors.email.message}</span>}<inputtype="submit"value="Submit"/></form>);};

Validate your data with powerful decoders.

npm

importReactfrom'react';import{useForm}from'react-hook-form';import{ioTsResolver}from'@hookform/resolvers/io-ts';importtfrom'io-ts';// you don't have to use io-ts-types, but it's very usefulimportttfrom'io-ts-types';constschema=t.type({username: t.string,age: tt.NumberFromString,});constApp=()=>{const{ register, handleSubmit }=useForm({resolver: ioTsResolver(schema),});return(<formonSubmit={handleSubmit((d)=>console.log(d))}><input{...register('username')}/><inputtype="number"{...register('age')}/><inputtype="submit"/></form>);};exportdefaultApp;

A small, simple, and fast JS validator

npm

import{useForm}from'react-hook-form';import{nopeResolver}from'@hookform/resolvers/nope';importNopefrom'nope-validator';constschema=Nope.object().shape({name: Nope.string().required(),age: Nope.number().required(),});constApp=()=>{const{ register, handleSubmit }=useForm({resolver: nopeResolver(schema),});return(<formonSubmit={handleSubmit((d)=>console.log(d))}><input{...register('name')}/><inputtype="number"{...register('age')}/><inputtype="submit"/></form>);};

TypeScript-first schema validation with static type inference

npm

import{useForm}from'react-hook-form';import{computedTypesResolver}from'@hookform/resolvers/computed-types';importSchema,{number,string}from'computed-types';constschema=Schema({username: string.min(1).error('username field is required'),age: number,});constApp=()=>{const{
register,
handleSubmit,formState: { errors },}=useForm({resolver: computedTypesResolver(schema),});return(<formonSubmit={handleSubmit((d)=>console.log(d))}><input{...register('name')}/>{errors.name?.message&&<p>{errors.name?.message}</p>}<inputtype="number"{...register('age',{valueAsNumber: true})}/>{errors.age?.message&&<p>{errors.age?.message}</p>}<inputtype="submit"/></form>);};

Static and runtime type assertion library with no dependencies

npm

import{useForm}from'react-hook-form';import{typanionResolver}from'@hookform/resolvers/typanion';import*astfrom'typanion';constisUser=t.isObject({username: t.applyCascade(t.isString(),[t.hasMinLength(1)]),age: t.applyCascade(t.isNumber(),[t.isInteger(),t.isInInclusiveRange(1,100),]),});constApp=()=>{const{
register,
handleSubmit,formState: { errors },}=useForm({resolver: typanionResolver(isUser),});return(<formonSubmit={handleSubmit((d)=>console.log(d))}><input{...register('name')}/>{errors.name?.message&&<p>{errors.name?.message}</p>}<inputtype="number"{...register('age')}/>{errors.age?.message&&<p>{errors.age?.message}</p>}<inputtype="submit"/></form>);};

The fastest JSON validator for Node.js and browser

npm

import{useForm}from'react-hook-form';import{ajvResolver}from'@hookform/resolvers/ajv';// must use `minLength: 1` to implement required fieldconstschema={type: 'object',properties: {username: {type: 'string',minLength: 1,errorMessage: {minLength: 'username field is required'},},password: {type: 'string',minLength: 1,errorMessage: {minLength: 'password field is required'},},},required: ['username','password'],additionalProperties: false,};constApp=()=>{const{
register,
handleSubmit,formState: { errors },}=useForm({resolver: ajvResolver(schema),});return(<formonSubmit={handleSubmit((data)=>console.log(data))}><input{...register('username')}/>{errors.username&&<span>{errors.username.message}</span>}<input{...register('password')}/>{errors.password&&<span>{errors.password.message}</span>}<buttontype="submit">submit</button></form>);};

JSON Schema Type Builder with Static Type Resolution for TypeScript

npm

With ValueCheck

import{useForm}from'react-hook-form';import{typeboxResolver}from'@hookform/resolvers/typebox';import{Type}from'@sinclair/typebox';constschema=Type.Object({username: Type.String({minLength: 1}),password: Type.String({minLength: 1}),});constApp=()=>{const{ register, handleSubmit }=useForm({resolver: typeboxResolver(schema),});return(<formonSubmit={handleSubmit((d)=>console.log(d))}><input{...register('username')}/><inputtype="password"{...register('password')}/><inputtype="submit"/></form>);};

With TypeCompiler

A high-performance JIT of TypeBox, read more

import{useForm}from'react-hook-form';import{typeboxResolver}from'@hookform/resolvers/typebox';import{Type}from'@sinclair/typebox';import{TypeCompiler}from'@sinclair/typebox/compiler';constschema=Type.Object({username: Type.String({minLength: 1}),password: Type.String({minLength: 1}),});consttypecheck=TypeCompiler.Compile(schema);constApp=()=>{const{ register, handleSubmit }=useForm({resolver: typeboxResolver(typecheck),});return(<formonSubmit={handleSubmit((d)=>console.log(d))}><input{...register('username')}/><inputtype="password"{...register('password')}/><inputtype="submit"/></form>);};

Custom/third-party types (e.g. ElysiaJS's t.Files())

typeboxResolver validates using @sinclair/typebox's own Value.Errors/Value.Check, so any schema Kind not built into TypeBox (custom types, or ones defined by a third-party library such as ElysiaJS) must be registered with TypeBox's own TypeRegistry (and FormatRegistry for string formats) before it's used — this is a TypeBox-level extension point, not something @hookform/resolvers wraps or needs to expose separately:

import{TypeRegistry}from'@sinclair/typebox';if(!TypeRegistry.Has('Files')){TypeRegistry.Set('Files',(schema,value)=>Array.isArray(value));}

Make sure this registration runs against the same @sinclair/typebox module instance the schema was built with — if a library vendors its own copy of @sinclair/typebox instead of relying on the shared peer dependency, its registrations won't be visible here, and you'll see a runtime "Unknown type" error.

TypeScript's 1:1 validator, optimized from editor to runtime

npm

import{useForm}from'react-hook-form';import{arktypeResolver}from'@hookform/resolvers/arktype';import{type}from'arktype';constschema=type({username: 'string>1',password: 'string>1',});constApp=()=>{const{ register, handleSubmit }=useForm({resolver: arktypeResolver(schema),});return(<formonSubmit={handleSubmit((d)=>console.log(d))}><input{...register('username')}/><inputtype="password"{...register('password')}/><inputtype="submit"/></form>);};

The modular and type safe schema library for validating structural data

npm

import{useForm}from'react-hook-form';import{valibotResolver}from'@hookform/resolvers/valibot';import*asvfrom'valibot';constschema=v.object({username: v.pipe(v.string('username is required'),v.minLength(3,'Needs to be at least 3 characters'),v.endsWith('cool','Needs to end with `cool`'),),password: v.string('password is required'),});constApp=()=>{const{ register, handleSubmit }=useForm({resolver: valibotResolver(schema),});return(<formonSubmit={handleSubmit((d)=>console.log(d))}><input{...register('username')}/><inputtype="password"{...register('password')}/><inputtype="submit"/></form>);};

⚠️ If your schema uses v.transform(...) (or v.pipe(..., v.transform(...))) on a field, that field's parsed output type can differ from what the user actually types (its input type) — e.g. a string input transformed into a number. Passing a single generic to useForm<T> pins both the submitted values and the handleSubmit payload to that one type, which conflicts with valibotResolver, since it infers input and output separately. Either omit the generic and let it infer from resolver, or specify all three explicitly:

constschema=v.object({name: v.string(),number: v.pipe(v.string(),v.transform(Number)),});useForm<v.InferInput<typeofschema>,unknown,v.InferOutput<typeofschema>>({resolver: valibotResolver(schema),});

Universal adapter for schema validation, compatible with any validation library

npm

import{useForm}from'react-hook-form';import{typeschemaResolver}from'@hookform/resolvers/typeschema';import{z}from'zod';// Use your favorite validation libraryconstschema=z.object({username: z.string().min(1,{message: 'Required'}),password: z.number().min(1,{message: 'Required'}),});constApp=()=>{const{ register, handleSubmit }=useForm({resolver: typeschemaResolver(schema),});return(<formonSubmit={handleSubmit((d)=>console.log(d))}><input{...register('username')}/><inputtype="password"{...register('password')}/><inputtype="submit"/></form>);};

A powerful TypeScript framework that provides a fully-fledged functional effect system with a rich standard library.

npm

importReactfrom'react';import{useForm}from'react-hook-form';import{effectTsResolver}from'@hookform/resolvers/effect-ts';import{Schema}from'effect';constschema=Schema.Struct({username: Schema.String.pipe(Schema.nonEmptyString({message: ()=>'username required'}),),password: Schema.String.pipe(Schema.nonEmptyString({message: ()=>'password required'}),),});typeFormData=typeofschema.Type;interfaceProps{onSubmit: (data: FormData)=>void;}functionTestComponent({ onSubmit }: Props){const{
register,
handleSubmit,formState: { errors },// provide generic if TS has issues inferring types}=useForm<FormData>({resolver: effectTsResolver(schema),});return(<formonSubmit={handleSubmit(onSubmit)}><input{...register('username')}/>{errors.username&&<spanrole="alert">{errors.username.message}</span>}<input{...register('password')}/>{errors.password&&<spanrole="alert">{errors.password.message}</span>}<buttontype="submit">submit</button></form>);}

VineJS is a form data validation library for Node.js

npm

import{useForm}from'react-hook-form';import{vineResolver}from'@hookform/resolvers/vine';importvinefrom'@vinejs/vine';constschema=vine.create({username: vine.string().minLength(1),password: vine.string().minLength(1),});constApp=()=>{const{
register,
handleSubmit,formState: { errors },}=useForm({resolver: vineResolver(schema),});return(<formonSubmit={handleSubmit((d)=>console.log(d))}><input{...register('username')}/>{errors.username&&<spanrole="alert">{errors.username.message}</span>}<input{...register('password')}/>{errors.password&&<spanrole="alert">{errors.password.message}</span>}<buttontype="submit">submit</button></form>);};

A TypeScript-first library for building strongly-typed validation rules

npm

import{useForm}from'react-hook-form';import{fluentValidationResolver}from'@hookform/resolvers/fluentvalidation-ts';import{Validator}from'fluentvalidation-ts';classFormDataValidatorextendsValidator<FormData>{constructor(){super();this.ruleFor('username').notEmpty().withMessage('username is a required field');this.ruleFor('password').notEmpty().withMessage('password is a required field');}}constApp=()=>{const{
register,
handleSubmit,formState: { errors },}=useForm({resolver: fluentValidationResolver(newFormDataValidator()),});return(<formonSubmit={handleSubmit((d)=>console.log(d))}><input{...register('username')}/>{errors.username&&<spanrole="alert">{errors.username.message}</span>}<input{...register('password')}/>{errors.password&&<spanrole="alert">{errors.password.message}</span>}<buttontype="submit">submit</button></form>);};

A standard interface for TypeScript schema validation libraries

npm

Example zod

import{useForm}from'react-hook-form';import{standardSchemaResolver}from'@hookform/resolvers/standard-schema';import{z}from'zod';constschema=z.object({name: z.string().min(1,{message: 'Required'}),age: z.number().min(10),});constApp=()=>{const{
register,
handleSubmit,formState: { errors },}=useForm({resolver: standardSchemaResolver(schema),});return(<formonSubmit={handleSubmit((d)=>console.log(d))}><input{...register('name')}/>{errors.name?.message&&<p>{errors.name?.message}</p>}<inputtype="number"{...register('age',{valueAsNumber: true})}/>{errors.age?.message&&<p>{errors.age?.message}</p>}<inputtype="submit"/></form>);};

Example arkType

import{useForm}from'react-hook-form';import{standardSchemaResolver}from'@hookform/resolvers/standard-schema';import{type}from'arktype';constschema=type({username: 'string>1',password: 'string>1',});constApp=()=>{const{ register, handleSubmit }=useForm({resolver: standardSchemaResolver(schema),});return(<formonSubmit={handleSubmit((d)=>console.log(d))}><input{...register('username')}/><inputtype="password"{...register('password')}/><inputtype="submit"/></form>);};

A JSON Schema-based validator for JavaScript and TypeScript

npm

import{useForm}from'react-hook-form';import{ataResolver}from'@hookform/resolvers/ata-validator';constschema={type: 'object',properties: {username: {type: 'string',minLength: 1,},password: {type: 'string',minLength: 1,},},required: ['username','password'],};constApp=()=>{const{
register,
handleSubmit,formState: { errors },}=useForm({resolver: ataResolver(schema),});return(<formonSubmit={handleSubmit((data)=>console.log(data))}><input{...register('username')}/>{errors.username&&<span>{errors.username.message}</span>}<input{...register('password')}/>{errors.password&&<span>{errors.password.message}</span>}<buttontype="submit">submit</button></form>);};

Backers

Thanks go to all our backers! [Become a backer].

React Hook Form backers

Contributors

Thanks go to these wonderful people! [Become a contributor].

About

📋 Validation resolvers: Yup, Zod, Superstruct, Joi, Vest, Class Validator, io-ts, Nope, computed-types, typanion, Ajv, TypeBox, ArkType, Valibot, effect-ts, VineJS and Standard Schema

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

2.3k stars

Watchers

9 watching

Forks

Releases

Sponsor this project

Contributors

Languages