VeeValidate Logo


Component Injections

The default behavior for the plugin is to forcibly inject a validator instance for each component, which means each component has its own validator scope, that made sharing error messages between components relatively hard depending on your case.

You can rely on Vue's Provide/Inject API for this case, here is how it works:

By default the root Vue instance will be the only one to have a Validator instance, child components can further choose to either inject the parents tree validator or request a new one.

Here is how to request the parent's validator instance:

export default {
  inject: ['$validator'],
  // ...

export default {
  inject: {
    $validator: '$validator'
  // ...

If the first parent isn't able to provide a validator instance, the API will traverse the tree upwards looking for a parent that can.

Here is how to request a new validator instance for the component by setting a validator property on the component's VeeValidate constructor options:

export default {
  // ...
  $_veeValidate: {
    validator: 'new' // give me a new validator each time.
  // ...

You may also want to stop all automatic injections, and control when a component gets the ability to validate:

import Vue from 'vue';
import VeeValidate from 'vee-validate';

Vue.use(VeeValidate, { inject: false });

This will make the plugin stop instantiating a new validator for each component instance, excluding the root instance.

The errorBag and the fields objects will be also shared along with the validator, they will not be injected if the component does not have a validator instance.

Event Bus

There are situations where you need to 'transfer' or communicate errors from child components to their parent and vice versa. Although this is not related to the plugin, the need is common enough to require an example.

This example uses Vue.js event bus to communicate errors with the parent component, the parent component can also clear, or force validate the child component. Original example was created by @sproogen.

There is a better approach introduced by using the Provide/Inject API.

import bus from './bus'; import ChildComponent from './EventBusChild.vue'; export default { name: 'event-bus-example', components: { ChildComponent }, methods: { validateChild() { bus.$emit('validate'); }, clearChild() { bus.$emit('clear'); } }, created() { bus.$on('errors-changed', (errors) => { this.errors.clear(); errors.forEach((e) => { this.errors.add(e.field, e.msg, e.rule, e.scope); }); }); } };
<div class="columns is-multiline"> <child-component></child-component> <div class="column is-12"> <button class="button is-info" type="button" @click="validateChild">Validate Child</button> <button class="button is-danger" type="button" @click="clearChild">Clear Child Errors</button> </div> </div>

Backend Validation

You might need to also use your Laravel/Express or whatever back-end as your validation provider for numerous reasons, like checking if an email is unique since it is hard to implement on the client-side, we can achieve this using a custom rule and the reasoning feature:

import axios from 'axios'; // great ajax library.
import { Validator } from 'vee-validate';

const isUnique = (value) => {
  return'/api/validate/email', { email: value }).then((response) => {
    // Notice that we return an object containing both a valid property and a data property.
    return {
      data: {

// The messages getter may also accept a third parameter that includes the data we returned earlier.
Validator.extend('unique', {
  validate: isUnique,
  getMessage: (field, params, data) => {
    return data.message;

The following demo shows how it would work in action, note that it will only trigger if the user entered a valid email since the validator early exits upon first failure.

Since there is no real DB in this example, it's being simulated by a dynamic array.

import { Validator } from 'vee-validate'; export default { name: 'backend-example', data: () => ({ // simulate emails database emailsDB: [], email: null }), created() { // simulated DB. const isUnique = value => new Promise((resolve) => { setTimeout(() => { if (this.emailsDB.indexOf(value) === -1) { return resolve({ valid: true }); } return resolve({ valid: false, data: { message: `${value} is already taken.` } }); }, 200); }); Validator.extend('unique', { validate: isUnique, getMessage: (field, params, data) => data.message }); }, methods: { submit() { this.emailsDB.push(; = ''; this.$nextTick().then(() => { this.$validator.reset(); }); } } };
<div class="columns is-multiline"> <div class="column is-12"> <label class="label">Email</label> <p class="control has-icon has-icon-right"> <input name="email" v-model="email" v-validate="'required|email|unique'" :class="{'input': true, 'is-danger': errors.has('email') }" type="text" placeholder="Email"> <i v-show="errors.has('email')" class="fa fa-warning"></i> <span v-show="errors.has('email')" class="help is-danger">{{"{" + "{ errors.first('email') }" + "}"}}</span> </p> </div> <div class="column is-12"> <p class="control"> <button class="button is-primary" type="button" @click="submit">Submit</button> </p> </div> </div>