Simple, lightweight model-based validation for Vue.js 2.0

  • Contextified validators
  • Easy to use with custom validators (e.g. Moment.js)
  • Support for function composition
  • Validates different data sources: Vuex getters, computed values, etc.
  • High test coverage

Getting started

Package content

Simple, lightweight model-based validation for Vue.js

You can read the introduction post for more insight on how this solution differs from other validation libraries.


Package is installable via npm

npm install vuelidate --save

Basic usage

You can import the library and use as a Vue plugin to enable the functionality globally on all components containing validation configuration.

import Vue from 'vue'
import Vuelidate from 'vuelidate'

Alternatively it is possible to import a mixin directly to components in which it will be used.

import { validationMixin } from 'vuelidate'

var Component = Vue.extend({
  mixins: [validationMixin],
  validation: { ... }

If you prefer using require, it can be used instead of import statements. This works especially great with destructuring syntax.

const { validationMixin, default: Vuelidate } = require('vuelidate')
const { required, minLength } = require('vuelidate/lib/validators')

The browser-ready bundle is also provided in the package.

<script src="vuelidate/dist/vuelidate.min.js"></script>
// global

// local mixin
var validationMixin = window.vuelidate.validationMixin


Basic form

For each value you want to validate, you have to create a key inside validations options. You can specify when input becomes dirty by using appropriate event on your input box.

import { required, minLength, between } from 'vuelidate/lib/validators'

export default {
  data () {
    return {
      name: '',
      age: 0
  validations: {
    name: {
      minLength: minLength(4)
    age: {
      between: between(20, 30)

  .form-group(v-bind:class="{ 'form-group--error': $$error }")
    label.form__label Name
    input.form__input(v-model.trim="name" @input="$$touch()")
  span.form-group__message(v-if="!$") Field is required
  span.form-group__message(v-if="!$") Name must be longer than 3 letters.
    | name: {{ $ }}

  .form-group(v-bind:class="{ 'form-group--error': $v.age.$error }")
    label.form__label Age
    input.form__input(v-model.trim="age" @blur="$v.age.$touch()")
  span.form-group__message(v-if="!$v.age.between") Must be between 20 and 30
    | age: {{ $v.age }}

  <div class="form-group" v-bind:class="{ 'form-group--error': $$error }">
    <label class="form__label">Name</label>
    <input class="form__input" v-model.trim="name" @input="$$touch()">
  </div><span class="form-group__message" v-if="!$">Field is required</span><span class="form-group__message" v-if="!$">Name must be longer than 3 letters.</span>
  <pre>name: {{ $ }}</pre>
  <div class="form-group" v-bind:class="{ 'form-group--error': $v.age.$error }">
    <label class="form__label">Age</label>
    <input class="form__input" v-model.trim="age" @blur="$v.age.$touch()">
  </div><span class="form-group__message" v-if="!$v.age.between">Must be between 20 and 30</span>
  <pre>age: {{ $v.age }}</pre>

Contextified validators

You can link related fields by contextified validators. An example of this being sameAs builtin validator.

import { required, sameAs, minLength } from 'vuelidate/lib/validators'

export default {
  data () {
    return {
      password: '',
      repeatPassword: ''
  validations: {
    password: {
      minLength: minLength(6)
    repeatPassword: {
      sameAsPassword: sameAs('password')

  .form-group(v-bind:class="{ 'form-group--error': $v.password.$error }")
    label.form__label Password
    input.form__input(v-model.trim="password" @input="$v.password.$touch()")
  span.form-group__message(v-if="!$v.password.required") Password is required.
  span.form-group__message(v-if="!$v.password.minLength") Password must be longer than 5 letters.

  .form-group(v-bind:class="{ 'form-group--error': $v.repeatPassword.$error }")
    label.form__label Repeat password
    input.form__input(v-model.trim="repeatPassword" @input="$v.repeatPassword.$touch()")
  span.form-group__message(v-if="!$v.repeatPassword.sameAsPassword") Passwords must be identical.
    | password: {{ $v.password }}
    | repeatPassword: {{ $v.repeatPassword }}

  <div class="form-group" v-bind:class="{ 'form-group--error': $v.password.$error }">
    <label class="form__label">Password</label>
    <input class="form__input" v-model.trim="password" @input="$v.password.$touch()">
  </div><span class="form-group__message" v-if="!$v.password.required">Password is required.</span><span class="form-group__message" v-if="!$v.password.minLength">Password must be longer than 5 letters.</span>
  <div class="form-group" v-bind:class="{ 'form-group--error': $v.repeatPassword.$error }">
    <label class="form__label">Repeat password</label>
    <input class="form__input" v-model.trim="repeatPassword" @input="$v.repeatPassword.$touch()">
  </div><span class="form-group__message" v-if="!$v.repeatPassword.sameAsPassword">Passwords must be identical.</span>
  <pre>password: {{ $v.password }}
repeatPassword: {{ $v.repeatPassword }}</pre>

Data nesting

You can nest validators to match your data as deep as you want. Parent validator errors out when any of its children reports an error. This might be very useful for overall form validation.

import { required } from 'vuelidate/lib/validators'

export default {
  data () {
    return {
      form: {
        nestedA: '',
        nestedB: ''
  validations: {
    form: {
      nestedA: {
      nestedB: {

  .form-group(v-bind:class="{ 'form-group--error': $v.form.nestedA.$error }")
    label.form__label Nested A
    input.form__input(v-model.trim="form.nestedA" @input="$v.form.nestedA.$touch()")
  span.form-group__message(v-if="!$v.form.nestedA.required") Field is required.
  .form-group(v-bind:class="{ 'form-group--error': $v.form.nestedB.$error }")
    label.form__label Nested B
    input.form__input(v-model.trim="form.nestedB" @input="$v.form.nestedB.$touch()")
  span.form-group__message(v-if="!$v.form.nestedB.required") Field is required.

  .form-group(v-bind:class="{ 'form-group--error': $v.form.$error }")
  span.form-group__message(v-if="$v.form.$error") Form is invalid.

  pre form: {{ $v.form }}

  <div class="form-group" v-bind:class="{ 'form-group--error': $v.form.nestedA.$error }">
    <label class="form__label">Nested A</label>
    <input class="form__input" v-model.trim="form.nestedA" @input="$v.form.nestedA.$touch()">
  </div><span class="form-group__message" v-if="!$v.form.nestedA.required">Field is required.</span>
  <div class="form-group" v-bind:class="{ 'form-group--error': $v.form.nestedB.$error }">
    <label class="form__label">Nested B</label>
    <input class="form__input" v-model.trim="form.nestedB" @input="$v.form.nestedB.$touch()">
  </div><span class="form-group__message" v-if="!$v.form.nestedB.required">Field is required.</span>
  <div class="form-group" v-bind:class="{ 'form-group--error': $v.form.$error }"></div><span class="form-group__message" v-if="$v.form.$error">Form is invalid.</span>
  <pre>form: {{ $v.form }}</pre>

Validation Groups

If you want to create a validator that groups many otherise unrelated fields together, you can create a validation group.

import { required } from 'vuelidate/lib/validators'

export default {
  data () {
    return {
      flatA: '',
      flatB: '',
      forGroup: {
        nested: ''
  validations: {
    flatA: { required },
    flatB: { required },
    forGroup: {
      nested: { required }
    validationGroup: ['flatA', 'flatB', 'forGroup.nested']

  .form-group(v-bind:class="{ 'form-group--error': $v.flatA.$error }")
    label.form__label Flat A
    input.form__input(v-model.trim="flatA" @input="$v.flatA.$touch()")
  span.form-group__message(v-if="!$v.flatA.required") Field is required.
  .form-group(v-bind:class="{ 'form-group--error': $v.flatB.$error }")
    label.form__label Flat B
    input.form__input(v-model.trim="flatB" @input="$v.flatB.$touch()")
  span.form-group__message(v-if="!$v.flatB.required") Field is required.
  .form-group(v-bind:class="{ 'form-group--error': $v.forGroup.nested.$error }")
    label.form__label Nested field
    input.form__input(v-model.trim="forGroup.nested" @input="$v.forGroup.nested.$touch()")
  span.form-group__message(v-if="!$v.forGroup.nested.required") Field is required.

  .form-group(v-bind:class="{ 'form-group--error': $v.validationGroup.$error }")
  span.form-group__message(v-if="$v.validationGroup.$error") Group is invalid.

    | validationGroup: {{ $v.validationGroup }}

  <div class="form-group" v-bind:class="{ 'form-group--error': $v.flatA.$error }">
    <label class="form__label">Flat A</label>
    <input class="form__input" v-model.trim="flatA" @input="$v.flatA.$touch()">
  </div><span class="form-group__message" v-if="!$v.flatA.required">Field is required.</span>
  <div class="form-group" v-bind:class="{ 'form-group--error': $v.flatB.$error }">
    <label class="form__label">Flat B</label>
    <input class="form__input" v-model.trim="flatB" @input="$v.flatB.$touch()">
  </div><span class="form-group__message" v-if="!$v.flatB.required">Field is required.</span>
  <div class="form-group" v-bind:class="{ 'form-group--error': $v.forGroup.nested.$error }">
    <label class="form__label">Nested field</label>
    <input class="form__input" v-model.trim="forGroup.nested" @input="$v.forGroup.nested.$touch()">
  </div><span class="form-group__message" v-if="!$v.forGroup.nested.required">Field is required.</span>
  <div class="form-group" v-bind:class="{ 'form-group--error': $v.validationGroup.$error }"></div><span class="form-group__message" v-if="$v.validationGroup.$error">Group is invalid.</span>
  <pre>validationGroup: {{ $v.validationGroup }}</pre>

Collections validation

Array support with $each keyword

import { required, minLength } from 'vuelidate/lib/validators'

export default {
  data () {
    return {
      people: [{
        name: 'John'
      }, {
        name: ''
  validations: {

    people: {
      minLength: minLength(2),
      $each: {
        name: {


  div(v-for="(person, index) in people")
    .form-group(v-bind:class="{ 'form-group--error': $v.people.$each[index].$error }")
      label.form__label Name for {{ index }}
      input.form__input(v-model.trim="" @input="$v.people.$each[index].name.$touch()")
    span.form-group__message(v-if="!$v.people.$each[index].name.required") Name is required.

    button.button(@click="people.push({name: ''})") Add
    button.button(@click="people.pop()") Remove
  .form-group(v-bind:class="{ 'form-group--error': $v.people.$error }")
  span.form-group__message(v-if="!$v.people.minLength") List must have at least 2 elements.
  span.form-group__message(v-else-if="!$v.people.required") List must not be empty.
  span.form-group__message(v-else-if="$v.people.$error") List is invalid.
  button.button(@click="$v.people.$touch") $touch
  button.button(@click="$v.people.$reset") $reset

    | people: {{ $v.people }}

  <div v-for="(person, index) in people">
    <div class="form-group" v-bind:class="{ 'form-group--error': $v.people.$each[index].$error }">
      <label class="form__label">Name for {{ index }}</label>
      <input class="form__input" v-model.trim="" @input="$v.people.$each[index].name.$touch()">
    </div><span class="form-group__message" v-if="!$v.people.$each[index].name.required">Name is required.</span>
    <button class="button" @click="people.push({name: ''})">Add</button>
    <button class="button" @click="people.pop()">Remove</button>
  <div class="form-group" v-bind:class="{ 'form-group--error': $v.people.$error }"></div><span class="form-group__message" v-if="!$v.people.minLength">List must have at least 2 elements.</span><span class="form-group__message" v-else-if="!$v.people.required">List must not be empty.</span><span class="form-group__message" v-else-if="$v.people.$error">List is invalid.</span>
  <button class="button" @click="$v.people.$touch">$touch</button>
  <button class="button" @click="$v.people.$reset">$reset</button>
  <pre>people: {{ $v.people }}</pre>

Asynchronous validation

Async support is provided out of the box. Just use a validator that returns a promise. Promise's success value is used for validation directly, failed promise just fails the validation and throws the error.

Any component's data has to be accessed synchronously for correct reactive behaviour. Store it as a variable in validator's scope if you need to use it in any asynchronous callback, for example in .then.

Validator is evaluated on every data change, as it is essentially a computed value. If you need to throttle an async call, do it on your data change event, not on the validator itself. You may end up with broken Vue observables otherwise.

import { required } from 'vuelidate/lib/validators'

export default {
  data () {
    return {
      username: ''
  validations: {
    username: {
      isUnique (value) {
        // standalone validator ideally should not assume a field is required
        if (value === '') return true

        // simulate async call, fail for all logins with even length
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            resolve(typeof value === 'string' && value.length % 2 !== 0)
          }, 350 + Math.random() * 300)

  .form-group(v-bind:class="{ 'form-group--error': $v.username.$error, 'form-group--loading': $v.username.$pending }")
    label.form__label Username
    input.form__input(v-model.trim="username" @input="$v.username.$touch()")
  span.form-group__message(v-if="!$v.username.required") Username is required.
  span.form-group__message(v-if="!$v.username.isUnique") This username is already registered.
    | username: {{ $v.username }}

  <div class="form-group" v-bind:class="{ 'form-group--error': $v.username.$error, 'form-group--loading': $v.username.$pending }">
    <label class="form__label">Username</label>
    <input class="form__input" v-model.trim="username" @input="$v.username.$touch()">
  </div><span class="form-group__message" v-if="!$v.username.required">Username is required.</span><span class="form-group__message" v-if="!$v.username.isUnique">This username is already registered.</span>
  <pre>username: {{ $v.username }}</pre>

The async/await syntax is fully supported. It works especially great in combination with Fetch API.

validations: {
  async isUnique (value) {
    if (value === '') return true
    const response = await fetch(`/api/unique/${value}`)
    return Boolean(await response.json())


There are two distinct structures present in vuelidate:

  • validations component option - the definition of your validation
  • $v structure - an object in your viewmodel that holds the validation state

$v values

$v model represents the current state of validation. It does so by defining a set of properties which hold the output of user defined validation functions, following the validations option structure. The presense of those special reserved keywords means that you cannot specify your own validators with that name.

$invalidbooleanIndicates the state of validation for given model. becomes true when any of it's child validators specified in options returns a falsy value. In case of validation groups, all grouped validators are considered.
$dirtybooleanA flag representing if the field under validation was touched by the user at least once. Usually it is used to decide if the message is supposed to be displayed to the end user. Flag is managed manually. You have to use $touch and $reset methods to manipulate it. The $dirty flag is considered true if given model was $touched or all of it's children are $dirty.
$errorbooleanConvinience flag to easily decide if a message should be displayed. It is a shorthand to $invalid && $dirty.
$pendingbooleanIndicates if any child async validator is currently pending. Always false if all validators are synchronous.
$eachobjectHolds all validation models of collection validator. Always preserves the keys of original model, so it can be safely referenced in the v-for loop iterating over your data using the same index.

$v methods

A set of methods to control the validation model. Accessible on every level of nesting. All methods are ment to be used on any event handler you wish. There is no extra syntax to decide when the dirty flag should be set. Just use standard @input or @blur bindings.

$touchSets the $dirty flag of the model and all its children to true recursively.
$resetSets the $dirty flag of the model and all its children to false recursively.

Config keywords

$eachobjectA definition of nested validation applied to each prop of given model separately. Perfect for validation arrays, but can be used with any object or collection.
$trackBystring || funcMust be a direct children of $each, but is optional. Defines the accessor to object's property by which $each tracks it's child models. Necessary to correctly preserve $dirty flag on random insertions. If this property not preset, the key is used for tracking.


vuelidate comes with a set of builtin validators that you can just require and use, but it doesn't end there. All of those are just simple predicates - functions of data into boolean, which denotes if data is valid. You can easily write your own or use any function in this shape from any library you already have, like _.conforomsTo from lodash or higher order functions and chains like R.cond from ramda. Think of the possibilities.

This documentation presents every builtin validator with short description and presents an example custom validator implementation to help understanding them and writing your own as easy as possible.

Builtin validators

To use any of builtin validators, you have to import it from vuelidate library.

import { required, maxLength } from 'vuelidate/lib/validators'

You can also import specific validators directly, to avoid loading unused ones in case your bundler doesn't support tree shaking. This is not required for Rollup or Webpack 2 among others.

import required from 'vuelidate/lib/validators/required'
import maxLength from 'vuelidate/lib/validators/maxLength'

It is possible to use validators directly in browser by using a browser-ready bundle. Keep in mind this will always load all builtin validators at once.

<script src="vuelidate/dist/validators.min.js"></script>
var required = validators.required
var maxLength = validators.maxLength

Here is a full list of provided validators.

NameMeta parametersDescription
requirednoneRequires non-empty data. Checks for empty arrays and strings containing only whitespaces.
minLengthmin lengthRequires the input to have a minimum specified length, inclusive. Works with arrays.
maxLengthmax lengthRequires the input to have a maximum specified length, inclusive. Works with arrays.
betweenmin, maxChecks if a number is in specified bounds. Min and max are both inclusive.
alphanoneAccepts only alphabet characters.
alphaNumnoneAccepts only alphanumerics.
emailnoneAccepts valid email addresses. Keep in mind you still have to carefully verify it on your server, as it is impossible to tell if the address is real without sending verification email.
sameAslocatorChecks for equality with a given property. Locator might be either a sibling property name or a function, that will get your component as this and nested model which sibling properties under second parameter.
orvalidators...Passes when at least one of provided validators passes.
andvalidators...Passes when all of provided validators passes.

Custom validators

You can easily write custom validators and combine them with builtin ones, as those are just a simple predicate functions.

Let's take a simple required as an example. It's simply just a function that, given it's input value, returns if the data is valid or not.

export default value => {
  if (Array.isArray(value)) return !!value.length

  return value === undefined || value === null
    ? false
    : !!String(value).length

If your validator needs extra parameters, you can simply create a higher order function that returns the actual predicate, like in between.

import required from './required'

export default (min, max) =>
  value => !required(value) || (!/\s/.test(value) && Number(min) <= value && Number(max) >= value)

In more complex cases when access to the whole model is necessary, like sameAs, make use of the function context (this) to access any value on your component or use provided parentVm to access sibling properties.

export default equalTo => function (value, parentVm) {
  if (value === null || value === undefined || value === '') {
    return true

  const compareTo = typeof equalTo === 'function'
    ?, parentVm)
    : parentVm[equalTo]
  return value === compareTo

Created by Damian Dulisz & PaweĊ‚ Grabarz

With love from Monterail