How to Validate Forms with Custom Validation Rules in Element UI

You can validate forms with custom validation rules in Element UI by supplying rule objects to the rules prop of <el-form> or <el-form-item>, which are then processed by the internal AsyncValidator engine.

Element UI (ElemeFE/element) provides a robust form validation system that allows developers to validate forms with custom validation rules using both form-level and form-item-level configurations. The validation engine leverages the async-validator library to execute rules based on triggers like blur or change.

Understanding the Validation Architecture

The validation system combines three distinct sources of rules, defined in packages/form/src/form.vue and packages/form/src/form-item.vue.

Form-Level Rules

In packages/form/src/form.vue at lines 24-26, the rules prop accepts an object mapping field names to rule arrays:

rules: {
  type: Object,
  default: null
}

These rules are injected into all child <el-form-item> components via Vue's provide/inject pattern, making them available for every field in the form.

Form-Item-Level Rules

Individual fields can override or extend form-level rules using the rules prop defined at lines 70-71 of packages/form/src/form-item.vue:

rules: {
  type: [Object, Array],
  default: null
}

Additionally, the required prop (lines 66-69) provides a shortcut that automatically generates a { required: true } rule when set to true.

How Custom Validation Rules Work

When validation triggers, the FormItem component executes a multi-step process defined in packages/form/src/form-item.vue.

The Rule Merging Process

The getRules() method (lines 57-66) concatenates three rule sources in order:

  1. Form-level rules for the specific prop name
  2. Form-item-level rules prop
  3. Auto-generated rule if required is true

This creates a comprehensive rule array that respects both global and local configurations.

Trigger-Based Filtering

Before execution, getFilteredRule(trigger) (lines 67-78) filters the merged rules based on their trigger property. This ensures that blur rules only run on blur events and change rules only run on change events, optimizing performance and user experience.

AsyncValidator Execution

The actual validation occurs in the validate() method (lines 94-124):

  1. Descriptor creation: Rules are assigned to a descriptor object where the key is the field prop
  2. Validation: An AsyncValidator instance processes the current fieldValue against the descriptor
  3. State management: On success, validateState becomes 'success'; on failure, it becomes 'error' with the message stored in validateMessage
  4. Event emission: The field emits 'validate' to the parent form for result aggregation

Implementing Custom Validation Rules

Form-Level Custom Validators

Define complex validation logic in the form's rules object using the validator function:

<template>
  <el-form :model="form" :rules="rules" ref="myForm">
    <el-form-item label="Username" prop="username">
      <el-input v-model="form.username" />
    </el-form-item>
    <el-button @click="submit">Submit</el-button>
  </el-form>
</template>

<script>
export default {
  data() {
    return {
      form: { username: '' },
      rules: {
        username: [
          { required: true, message: 'Username is required', trigger: 'blur' },
          {
            validator: (rule, value, callback) => {
              const ok = /^[a-zA-Z0-9_]+$/.test(value);
              ok ? callback() : callback(new Error('Only letters, numbers, and _ are allowed'));
            },
            trigger: 'blur'
          }
        ]
      }
    };
  },
  methods: {
    submit() {
      this.$refs.myForm.validate(valid => {
        if (valid) alert('Validation passed');
      });
    }
  }
};
</script>

Item-Level Custom Validators

Override form-level rules or add field-specific validation using the rules prop on individual items:

<template>
  <el-form :model="user" :rules="formRules">
    <el-form-item label="Email" prop="email">
      <el-input v-model="user.email" />
    </el-form-item>
    <el-form-item label="Password" prop="password" :rules="passwordRules">
      <el-input type="password" v-model="user.password" />
    </el-form-item>
  </el-form>
</template>

<script>
export default {
  data() {
    return {
      user: { email: '', password: '' },
      formRules: {
        email: [{ required: true, message: 'Email required', trigger: 'blur' }]
      },
      passwordRules: [
        { required: true, message: 'Password required', trigger: 'blur' },
        {
          validator: (rule, value, cb) => {
            setTimeout(() => {
              const strong = /^(?=.*[A-Z])(?=.*\d).{8,}$/.test(value);
              strong ? cb() : cb(new Error('Weak password'));
            }, 300);
          },
          trigger: 'blur'
        }
      ]
    };
  }
};
</script>

Using the Required Shortcut

For simple presence validation, use the required boolean prop instead of writing a rule object:

<el-form-item label="Phone" prop="phone" :required="true">
  <el-input v-model="contact.phone" />
</el-form-item>

When required is set to true, FormItem.getRules() automatically injects { required: true } into the validation chain (lines 60-61 of packages/form/src/form-item.vue).

Programmatic Validation Control

Beyond declarative rules, the components expose methods for manual validation management.

Triggering Validation Manually

Call validate() on the form instance to check all fields:

this.$refs.formRef.validate((valid, fields) => {
  if (valid) {
    console.log('Submit form');
  } else {
    console.log('Validation failed', fields);
  }
});

Clearing Validation State

Remove error messages without resetting values:

this.$refs.formRef.clearValidate();
// Or for a specific field
this.$refs.formItemRef.clearValidate();

Resetting Fields

Restore fields to their initial values and clear validation:

this.$refs.formRef.resetFields();

Dynamic Rule Changes

Set validateOnRuleChange to false (lines 41-44 of packages/form/src/form.vue) to prevent automatic re-validation when the rules object changes:

<el-form :rules="dynamicRules" :validate-on-rule-change="false">
  <!-- fields -->
</el-form>

Summary

  • Form-level rules defined in packages/form/src/form.vue (lines 24-26) provide global validation logic injected into all child components.
  • Form-item-level rules in packages/form/src/form-item.vue (lines 70-71) allow field-specific overrides and extensions.
  • Rule merging occurs in FormItem.getRules() (lines 57-66), concatenating form rules, item rules, and the required shortcut.
  • Trigger filtering via getFilteredRule() (lines 67-78) ensures rules only execute on specified events (blur, change).
  • AsyncValidator handles the actual validation logic in FormItem.validate() (lines 94-124), managing success/error states and emitting events to the parent form.

Frequently Asked Questions

How do I trigger validation on blur versus change events?

Use the trigger property in your rule definition. Set trigger: 'blur' to validate when the field loses focus, or trigger: 'change' to validate when the value changes. The getFilteredRule(trigger) method in packages/form/src/form-item.vue (lines 67-78) filters rules based on this property, ensuring only matching triggers execute during validation events.

Can I use async validators in Element UI forms?

Yes, the validation engine supports asynchronous validation through the async-validator library. Supply a validator function that accepts a callback as its third argument, and call callback() for success or callback(new Error('message')) for failure. For async operations like API calls, execute the logic inside the validator and invoke the callback when the operation completes, as shown in the password validation example using setTimeout.

How do I reset validation errors programmatically?

Call the clearValidate() method on either the form instance or individual form-item instances. For the entire form, use this.$refs.formRef.clearValidate(). To reset a specific field, reference the form-item directly. This method clears the validateState and validateMessage properties without modifying the field values. To reset both values and validation states, use resetFields() instead.

What is the difference between form-level and form-item-level rules?

Form-level rules, defined on the <el-form> component's rules prop (lines 24-26 of packages/form/src/form.vue), apply globally to all fields with matching prop names and are injected into child components. Form-item-level rules, defined on individual <el-form-item> components (lines 70-71 of packages/form/src/form-item.vue), override or extend form-level rules for specific fields. When validation runs, FormItem.getRules() (lines 57-66) merges both sources, with form-item rules taking precedence in the concatenation order.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →