Integrate Yup with VeeValidate 4.x in Vue 3: A Practical Guide
Learn how to bind a Yup schema to VeeValidate 4.x, see a ready‑to‑use example, understand limits and avoid common pitfalls.
16 Mar 2026, 22:24 UTC

Why bind a Yup schema to VeeValidate?
When a Vue 3 project already uses Yup for API request validation, re‑using the same schema in the UI keeps rules in sync and reduces duplication. VeeValidate 4.x ships a yupResolver that translates Yup’s validate method into the shape VeeValidate expects, enabling declarative form validation with <Form> and <Field> components.
Step 1 – Install the required packages
Run the following in the project root (requires npm or yarn and Node ≥ 14):
npm install vee-validate@^4 @vee-validate/yup yup
Make sure the installed VeeValidate version is 4.x; the resolver is not available in 3.x.
Step 2 – Define a Yup schema
Place the schema in a separate file (e.g., src/schemas/login.js) so it can be reused elsewhere:
import * as yup from 'yup';
export const loginSchema = yup.object({
email: yup.string().required('Email is required').email('Must be a valid email'),
password: yup.string().required('Password is required').min(8, 'Password must be at least 8 characters')
});
When your form has nested fields, use yup.object() nesting or dot‑notation keys to match the field names.
Step 3 – Connect the schema to VeeValidate
There are two common ways to wire the resolver: via the <Form> component or the useForm hook. The example below uses the hook for clarity.
import { defineComponent } from 'vue';
import { Form, Field, ErrorMessage, useForm } from 'vee-validate';
import { yupResolver } from '@vee-validate/yup';
import { loginSchema } from '@/schemas/login';
export default defineComponent({
name: 'LoginForm',
setup() {
const { handleSubmit, errors } = useForm({
validationSchema: yupResolver(loginSchema)
});
const onSubmit = handleSubmit(values => {
console.log('Form values', values);
});
return { onSubmit, errors };
}
});
The component template then looks like:
<template>
<Form @submit="onSubmit">
<label>Email:</label>
<Field name="email" type="email" />
<ErrorMessage name="email" />
<label>Password:</label>
<Field name="password" type="password" />
<ErrorMessage name="password" />
<button type="submit">Login</button>
</Form>
</template>
What happens under the hood?
The yupResolver calls schema.validate(values, { abortEarly: false }) and converts any ValidationError into an object where keys are field names and values are the first error message for that key. VeeValidate then populates its errors state, which the <ErrorMessage> component renders.
Step 4 – Verify the integration
- Run
npm run devand navigate to the form page. - Leave both fields empty and click
Login. Error messages should appear beneath each field. - Open Vue Devtools, inspect the component, and verify that
errorscontains{ email: 'Email is required', password: 'Password is required' }. - Enter an invalid email (e.g.,
foo) and a short password (e.g.,123) and confirm that the specific Yup messages are shown. - Add a custom async test to the schema:
Then submit again – the form should wait until the promise resolves before showing the error.password: yup.string() .required() .min(8) .test('not-weak', 'Password is too weak', async value => { // Simulate API call await new Promise(r => setTimeout(r, 200)); return !['12345678', 'password'].includes(value); })
Common pitfalls and limits
- Field name mismatch: VeeValidate silently skips validation if a
Fieldname does not match a schema key. Double‑check spelling and case. - Missing resolver package:
@vee-validate/yupmust be installed. Without it,yupResolveris undefined. - Using VeeValidate 3.x: The resolver was introduced in 4.x. Verify
npm list vee-validateshows a 4.x major version. - Large schemas: Validation can become slow if the schema has many fields or heavy async tests. Consider splitting into smaller schemas or using
yup.lazyfor optional groups. - Async test return type: Async tests must return a
Promise. Returning a plain boolean will be treated as synchronous and may hide errors. - Dynamic forms: When the field list changes at runtime, rebuild the schema (or call
resetFormwith the new resolver) so VeeValidate re‑evaluates the new keys.
Dynamic form example – rebuilding the schema
Suppose the form shows a “subscribe” checkbox that, when checked, adds a newsletterFrequency field. The schema must be regenerated whenever the checkbox state changes:
const baseSchema = yup.object({
email: yup.string().required().email(),
password: yup.string().required().min(8)
});
const dynamicSchema = computed(() => {
return props.subscribe
? baseSchema.shape({ newsletterFrequency: yup.string().required() })
: baseSchema;
});
const { handleSubmit } = useForm({
validationSchema: yupResolver(dynamicSchema)
});
When props.subscribe toggles, the validationSchema prop updates and VeeValidate re‑evaluates the new fields.
Final checklist before deployment
- Confirm VeeValidate 4.x and @vee-validate/yup are installed.
- Ensure each
<Field>name matches a key in the Yup schema. - Test both synchronous and asynchronous validation paths.
- Verify that error messages appear in the UI and that the
errorsobject contains the expected keys. - For dynamic forms, rebuild the schema or reset the form when the field set changes.
- Measure performance on large schemas; consider lazy or split schemas if needed.
With this pattern in place, your Vue 3 forms will benefit from Yup’s expressive validation syntax while keeping the UI logic clean and declarative.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.