# How Formily Handles Validation Errors: From Core Logic to UI Display

> Learn how Formily handles validation errors. Discover its core logic, structured feedback objects, and reactive UI display for seamless error management.

- Repository: [Alibaba/formily](https://github.com/alibaba/formily)
- Tags: deep-dive
- Published: 2026-02-24

---

**Formily processes validation errors by executing rules through the core Field model, converting results into structured Feedback objects, and exposing reactive getters like `selfErrors` that UI components consume to render messages.**

Formily is Alibaba's high-performance form solution designed for enterprise-grade applications. Its validation system decouples rule execution from presentation, using a reactive feedback mechanism that bridges the core data layer with framework-specific UI components. Understanding this architecture allows developers to debug complex validation scenarios and implement custom error displays effectively.

## The Core Validation Architecture

Formily's validation flow revolves around three interconnected concepts that separate concerns between data validation and UI rendering:

- **Validator** – A set of rules (required, max, custom functions) defined on a field's `validator` property and parsed by **@formily/validator**.
- **Feedback** – A unified data structure containing `type`, `messages`, `code`, and `triggerType` that stores validation results.
- **UI Bindings** – Components like `FormItem` read the field's `selfErrors`, `selfWarnings`, `selfSuccesses`, and `validateStatus` to render visual indicators.

When a field value changes, Formily automatically runs validation, converts the results into feedback objects, stores them on the field instance, and UI components reactively display the updated messages.

## Validation Execution Flow

### Triggering Validation

Validation can be triggered on multiple lifecycles including `onInput`, `onFocus`, `onBlur`, or explicit calls to `.validate()`. In [`packages/core/src/models/Field.ts`](https://github.com/alibaba/formily/blob/main/packages/core/src/models/Field.ts), the `validate` method initiates this process:

```typescript
// packages/core/src/models/Field.ts
validate = (triggerType?: ValidatorTriggerType) => {
  return batchValidate(this, `${this.address}.**`, triggerType)
}

```

This method delegates to `batchValidate` in [`packages/core/src/shared/internals.ts`](https://github.com/alibaba/formily/blob/main/packages/core/src/shared/internals.ts), which ultimately invokes `validateToFeedbacks` to process the field's specific rules.

### Rule Execution in @formily/validator

The `validateToFeedbacks` function calls the library-level `validate` function from the validator package to execute rules against the current value:

```typescript
// packages/core/src/shared/internals.ts
const results = await validate(
  field.value,
  field.validator,
  {
    triggerType,
    validateFirst: field.props.validateFirst ?? field.form.props.validateFirst,
    context: { field, form: field.form },
  }
)

```

The `validate` function in [`packages/validator/src/validator.ts`](https://github.com/alibaba/formily/blob/main/packages/validator/src/validator.ts) iterates over each parsed rule and returns a results object structured as `{ error: ['msg1'], warning: [], success: [] }`.

### Transforming Results to Feedback

After validation completes, each result type is converted into a `Feedback` object and stored on the field:

```typescript
// packages/core/src/shared/internals.ts
each(results, (messages, type: FieldFeedbackTypes) => {
  field.setFeedback({
    triggerType,
    type,
    code: pascalCase(`validate-${type}`),
    messages,
  })
})

```

### Persisting Feedback State

The `field.setFeedback` method forwards to `updateFeedback` in the internals module:

```typescript
// packages/core/src/models/Field.ts
setFeedback = (feedback?: IFieldFeedback) => {
  updateFeedback(this, feedback)
}

```

The `updateFeedback` function merges new feedback with existing entries, removes empty results, and maintains the `feedbacks` array on the field instance.

## Accessing Validation State

### Storage Structure

Each `Field` instance maintains a `feedbacks: IFieldFeedback[]` array that serves as an observable store for all validation messages.

### Query Helpers

Formily provides utility functions in [`packages/core/src/shared/internals.ts`](https://github.com/alibaba/formily/blob/main/packages/core/src/shared/internals.ts) to filter and retrieve feedback:

- `queryFeedbacks(field, search?)` – Filters feedback by type, address, or other criteria.
- `queryFeedbackMessages(field, {type: 'error'})` – Returns only message strings for the specified type.

### Computed Getters

The `Field` model exposes computed getters that UI components consume:

```typescript
// packages/core/src/models/Field.ts
get selfErrors(): FeedbackMessage {
  return queryFeedbackMessages(this, { type: 'error' })
}
// Similarly: selfWarnings, selfSuccesses, validateStatus

```

These getters automatically update when the underlying `feedbacks` array changes, providing reactive access to the current validation state.

## Rendering Errors in UI Components

UI components connect to the field's validation state through the `connect` and `mapProps` utilities. The `FormItem` component in [`packages/element/src/form-item/index.ts`](https://github.com/alibaba/formily/blob/main/packages/element/src/form-item/index.ts) demonstrates this pattern:

```typescript
// packages/element/src/form-item/index.ts (excerpt)
const Item = connect(
  FormBaseItem,
  mapProps(
    { validateStatus: true, title: 'label', required: true },
    (props, field) => {
      if (isVoidField(field) || !field) return props
      const takeMessage = () => {
        if (field.validating) return
        if (props.feedbackText) return props.feedbackText
        if (field.selfErrors.length) return field.selfErrors
        if (field.selfWarnings.length) return field.selfWarnings
        if (field.selfSuccesses.length) return field.selfSuccesses
      }
      const errorMessages = takeMessage()
      return {
        feedbackText: Array.isArray(errorMessages)
          ? errorMessages.join(', ')
          : errorMessages,
        extra: props.extra || field.description,
      }
    }
  )
)

```

The component maps `field.selfErrors` to the `feedbackText` prop and derives `validateStatus` from `field.validateStatus`. The template applies CSS classes like `${prefixCls}-error` based on these values, automatically rendering red borders, error icons, and message text when validation fails.

## Custom Validation Implementation

Here is a complete example demonstrating how Formily handles custom async validation:

```tsx
import { createForm } from '@formily/core'
import { Form, Field } from '@formily/react'

const form = createForm({
  values: { username: '' },
})

function MyForm() {
  return (
    <Form form={form}>
      <Field
        name="username"
        decorator={[FormItem, { label: 'Username' }]}
        component={['Input']}
        validator={[
          { required: true, message: 'Username is required' },
          { minLength: 4, message: 'At least 4 characters' },
          async (value) => {
            const exists = await checkUserExists(value)
            return exists ? 'User already taken' : null
          },
        ]}
      />
    </Form>
  )
}

```

When the user types, `onInput` triggers validation, which runs the three rules sequentially. Errors are stored in `field.selfErrors`, and `FormItem` automatically displays them without additional configuration.

## Summary

- **Validation triggers** execute through the Field model's `validate` method, which delegates to `batchValidate` and `validateToFeedbacks`.
- **Rule execution** occurs in `@formily/validator`, returning typed results that the core converts into Feedback objects.
- **State persistence** happens via `updateFeedback`, which manages the `feedbacks` array on each Field instance.
- **Reactive access** is provided through getters like `selfErrors` and `validateStatus`.
- **UI display** is handled by components like `FormItem` that map field state to visual elements using `connect` and `mapProps`.

## Frequently Asked Questions

### How does Formily trigger validation automatically?

Formily triggers validation automatically through lifecycle hooks on field interactions. When a user triggers `onInput`, `onFocus`, or `onBlur`, the Field model invokes `validateSelf`, which runs the validation pipeline and updates the feedback state reactively.

### What is the difference between selfErrors and feedbacks?

The `feedbacks` array is the raw storage containing all validation results with metadata like `triggerType` and `code`. The `selfErrors` getter is a computed property that filters this array to return only error-type messages, providing a convenient API for UI components to consume.

### How can I customize when validation errors appear?

Control validation timing through the `triggerType` parameter in validator rules or field props. You can set validation to run on specific events like `onBlur` rather than `onInput`, or use the `validateFirst` option to stop after the first error. These options are passed through the context to `validateToFeedbacks` in [`packages/core/src/shared/internals.ts`](https://github.com/alibaba/formily/blob/main/packages/core/src/shared/internals.ts).

### Where does Formily store validation error messages?

Formily stores validation error messages in the `feedbacks` observable array on each Field instance, defined in [`packages/core/src/models/Field.ts`](https://github.com/alibaba/formily/blob/main/packages/core/src/models/Field.ts). The messages are inserted via `setFeedback` and `updateFeedback`, then accessed through computed getters like `selfErrors` for display in UI components.