> For the complete documentation index, see [llms.txt](https://aurelia-1.gitbook.io/v1-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://aurelia-1.gitbook.io/v1-docs/aurelia-packages/validation.md).

# Validation

### Introduction

Aurelia's validation plugin provides a robust and flexible form and data validation system. It offers a fluent, declarative API that makes it easy to define validation rules for your application's models and forms.

The validation system supports:

* Declarative rule definition
* Customizable validation triggers
* Flexible error rendering
* Extensive rule types
* Internationalization support

#### Key Features

* Fluent rule configuration
* Multiple validation triggers
* Custom validation rules
* Easy error display
* Seamless integration with Aurelia's binding system

### Installation and Setup

#### Installation

Install the validation plugin using npm or your chosen package installer:

```bash
npm install aurelia-validation
```

#### Plugin Configuration

In your application's main configuration file (typically `main.js` or `main.ts`), register the validation plugin:

```javascript
export function configure(aurelia) {
  aurelia.use
    .standardConfiguration()
    .plugin(PLATFORM.moduleName('aurelia-validation'));
}
```

#### Optional Configuration

You can customize the default validation trigger during plugin registration:

```javascript
export function configure(aurelia) {
  aurelia.use.plugin('aurelia-validation', config => {
    config.defaultValidationTrigger(validateTrigger.manual);
  });
}
```

### Validation Basics

#### Defining Rules

Aurelia's validation uses a fluent API to define validation rules. The primary class for defining rules is `ValidationRules`.

**Basic Rule Definition**

```javascript
import { ValidationRules } from 'aurelia-validation';

ValidationRules
  .ensure('firstName')
    .required()
  .ensure('email')
    .required()
    .email()
  .on(MyClass);
```

#### Rule Types

Aurelia provides several built-in validation rules:

| Rule                | Description                                                | Example             |
| ------------------- | ---------------------------------------------------------- | ------------------- |
| `required()`        | Ensures the property is not null, undefined, or whitespace | `.required()`       |
| `email()`           | Validates email format                                     | `.email()`          |
| `minLength(length)` | Validates minimum string length                            | `.minLength(3)`     |
| `maxLength(length)` | Validates maximum string length                            | `.maxLength(50)`    |
| `matches(regex)`    | Validates against a regular expression                     | `.matches(/^\d+$/)` |
| `min(value)`        | Validates minimum numeric value                            | `.min(0)`           |
| `max(value)`        | Validates maximum numeric value                            | `.max(100)`         |

**Custom Display Names**

You can set custom display names for more meaningful error messages:

```javascript
ValidationRules
  .ensure('ssn').displayName('Social Security Number')
    .required()
    .matches(/\d{3}-\d{2}-\d{4}/)
  .on(Person);
```

#### Rule Sequencing

Rules are evaluated in parallel by default. Use `.then()` to create sequential rule evaluation:

```javascript
ValidationRules
  .ensure('email')
    .email()
    .required()
    .then()
    .satisfiesRule('emailNotInUse')
  .on(User);
```

**Customizing Error Messages**

Override default messages using `.withMessage()`:

```javascript
ValidationRules
  .ensure('password')
    .required().withMessage('Please enter a password')
    .minLength(8).withMessage('Password must be at least 8 characters long')
  .on(User);
```

#### Example: Complete Validation Setup

```javascript
export class RegistrationForm {
  firstName = '';
  email = '';
  password = '';

  constructor() {
    ValidationRules
      .ensure('firstName')
        .required().withMessage('First name is required')
        .minLength(2).withMessage('First name must be at least 2 characters')
      .ensure('email')
        .required().withMessage('Email is required')
        .email().withMessage('Please enter a valid email address')
      .ensure('password')
        .required().withMessage('Password is required')
        .minLength(8).withMessage('Password must be at least 8 characters')
      .on(this);
  }
}
```

### Validation Controllers

The ValidationController orchestrates validation execution and error rendering in your application. Controllers manage the validation lifecycle and maintain the current validation state.

#### Creating a Controller

You can create a controller using dependency injection:

```javascript
import { inject, NewInstance } from 'aurelia-dependency-injection';
import { ValidationController } from 'aurelia-validation';

@inject(NewInstance.of(ValidationController))
export class RegistrationForm {
  controller = null;

  constructor(controller) {
    this.controller = controller;
  }
}
```

Alternatively, use the ValidationControllerFactory:

```javascript
import { ValidationControllerFactory } from 'aurelia-validation';

@inject(ValidationControllerFactory)
export class RegistrationForm {
  controller = null;

  constructor(controllerFactory) {
    this.controller = controllerFactory.createForCurrentScope();
  }
}
```

#### Validation Triggers

Controllers support different validation triggers that determine when validation occurs:

* `blur`: Validates when an input loses focus (default)
* `focusout`: Validates when an element or its children lose focus
* `change`: Validates when the model value changes
* `changeOrBlur`: Validates on both change and blur events
* `changeOrFocusout`: Validates on both change and focusout events
* `manual`: Validation only occurs when explicitly called

Set the trigger in your view-model:

```javascript
import { validateTrigger } from 'aurelia-validation';

export class RegistrationForm {
  constructor(controller) {
    this.controller = controller;
    this.controller.validateTrigger = validateTrigger.changeOrBlur;
  }
}
```

#### Validate and Reset Methods

Trigger validation programmatically using the controller's methods:

```javascript
class RegistrationForm {
  async submit() {
    const result = await this.controller.validate();
    if (result.valid) {
      // proceed with form submission
    }
  }

  reset() {
    this.controller.reset();
  }
}
```

You can validate specific objects or properties:

```javascript
// Validate specific object
controller.validate({ object: person });

// Validate specific property
controller.validate({ object: person, propertyName: 'email' });
```

### Displaying Validation Errors

#### Built-in Error Display

The simplest way to display validation errors is using the `validation-errors` attribute and controller's errors property:

```html
<form>
  <div validation-errors.bind="errors">
    <input value.bind="email & validate">
    
    <ul if.bind="errors.length">
      <li repeat.for="error of errors">
        ${error.error.message}
      </li>
    </ul>
  </div>
</form>
```

#### Bootstrap Form Integration

Here's an example using Bootstrap form styling:

```html
<form>
  <div class="form-group" validation-errors.bind="firstNameErrors"
       class.bind="firstNameErrors.length ? 'has-error' : ''">
    <label for="firstName">First Name</label>
    <input type="text" class="form-control" id="firstName"
           value.bind="firstName & validate">
    <span class="help-block" repeat.for="error of firstNameErrors">
      ${error.error.message}
    </span>
  </div>
</form>
```

#### Custom Renderer Implementation

Create custom renderers for more control over error display:

```javascript
import { ValidationRenderer, RenderInstruction } from 'aurelia-validation';

export class CustomFormRenderer {
  render(instruction: RenderInstruction) {
    for (let { result, elements } of instruction.unrender) {
      for (let element of elements) {
        this.removeError(element, result);
      }
    }

    for (let { result, elements } of instruction.render) {
      for (let element of elements) {
        this.addError(element, result);
      }
    }
  }

  addError(element, result) {
    if (result.valid) return;

    const formGroup = element.closest('.form-group');
    if (!formGroup) return;

    // Add error class
    formGroup.classList.add('has-error');

    // Add error message
    const message = document.createElement('div');
    message.className = 'validation-error';
    message.textContent = result.message;
    message.id = `validation-message-${result.id}`;
    formGroup.appendChild(message);
  }

  removeError(element, result) {
    if (result.valid) return;

    const formGroup = element.closest('.form-group');
    if (!formGroup) return;

    // Remove error class
    formGroup.classList.remove('has-error');

    // Remove error message
    const message = formGroup.querySelector(`#validation-message-${result.id}`);
    if (message) {
      formGroup.removeChild(message);
    }
  }
}
```

Register your custom renderer with the controller:

```javascript
export class RegistrationForm {
  constructor(controller) {
    this.controller = controller;
    this.renderer = new CustomFormRenderer();
    this.controller.addRenderer(this.renderer);
  }

  detached() {
    this.controller.removeRenderer(this.renderer);
  }
}
```

#### Renderer Binding Context

When using validation-errors, you can specify both the errors and the controller:

```html
<div validation-errors="errors.bind: myErrors; controller.bind: myController">
  <input value.bind="email & validate:myController">
  <span repeat.for="error of myErrors">
    ${error.error.message}
  </span>
</div>
```

{% hint style="info" %}
Always remove custom renderers in the detached lifecycle method to prevent memory leaks and ensure proper cleanup.
{% endhint %}

### Advanced Validation Techniques

#### Conditional Validation

Implement conditional validation using the `.when()` method:

```javascript
ValidationRules
  .ensure('alternateEmail')
    .email()
    .required()
      .when(person => person.needsAlternateContact)
    .withMessage('Alternate email is required when alternate contact is enabled')
  .on(Person);
```

#### Custom Rules

Create reusable custom validation rules using `ValidationRules.customRule()`:

```javascript
// Define a custom date validation rule
ValidationRules.customRule(
  'date',
  (value, obj) => value === null || value === undefined || value instanceof Date,
  '${$displayName} must be a valid date.'
);

// Define a custom rule with parameters
ValidationRules.customRule(
  'numericRange',
  (value, obj, min, max) => {
    if (value === null || value === undefined) return true;
    return Number.isInteger(value) && value >= min && value <= max;
  },
  '${$displayName} must be between ${$config.min} and ${$config.max}.',
  (min, max) => ({ min, max })
);

// Using custom rules
ValidationRules
  .ensure('birthDate')
    .required()
    .satisfiesRule('date')
  .ensure('age')
    .satisfiesRule('numericRange', 18, 100)
  .on(Person);
```

#### Entity Validation

Validate entire objects using controller's `addObject()` method:

```javascript
export class OrderForm {
  constructor(controller) {
    this.controller = controller;
    this.order = new Order();
    
    // Add object for validation
    this.controller.addObject(this.order);
  }

  // Validate entire object
  async submit() {
    const result = await this.controller.validate();
    if (result.valid) {
      await this.submitOrder();
    }
  }

  detached() {
    // Clean up
    this.controller.removeObject(this.order);
  }
}
```

Define object-level rules using `ensureObject()`:

```javascript
ValidationRules
  .ensureObject()
    .satisfies(obj => obj.endDate > obj.startDate)
    .withMessage('End date must be after start date')
  .on(DateRange);
```

### Integration

#### Working with I18N

Integrate validation messages with aurelia-i18n:

```javascript
import { I18N } from 'aurelia-i18n';
import { ValidationMessageProvider } from 'aurelia-validation';

// Configure during app startup
export function configure(aurelia) {
  aurelia.use
    .standardConfiguration()
    .plugin('aurelia-i18n')
    .plugin('aurelia-validation');

  // Override message provider
  ValidationMessageProvider.prototype.getMessage = function(key) {
    const i18n = aurelia.container.get(I18N);
    const translation = i18n.tr(`validationMessages.${key}`);
    return this.parser.parse(translation);
  };

  ValidationMessageProvider.prototype.getDisplayName = function(propertyName, displayName) {
    if (displayName) return displayName;
    const i18n = aurelia.container.get(I18N);
    return i18n.tr(propertyName);
  };
}
```

Translation files example:

```json
{
  "validationMessages": {
    "required": "${$displayName} is required",
    "email": "${$displayName} must be a valid email",
    "minLength": "${$displayName} must be at least ${$config.length} characters"
  },
  "propertyNames": {
    "firstName": "First Name",
    "email": "Email Address"
  }
}
```

### API Reference

#### ValidationRules

Core methods for defining validation rules:

| Method                 | Description                              |
| ---------------------- | ---------------------------------------- |
| `ensure(property)`     | Targets a property for validation        |
| `ensureObject()`       | Targets the entire object for validation |
| `required()`           | Validates presence                       |
| `email()`              | Validates email format                   |
| `minLength(length)`    | Validates minimum length                 |
| `maxLength(length)`    | Validates maximum length                 |
| `matches(regex)`       | Validates against regular expression     |
| `satisfies(condition)` | Custom validation function               |
| `satisfiesRule(name)`  | Uses a custom rule                       |
| `when(condition)`      | Conditional validation                   |
| `withMessage(message)` | Custom error message                     |
| `on(target)`           | Applies rules to class/object            |

#### ValidationController

Core controller methods:

| Method                                     | Description                               |
| ------------------------------------------ | ----------------------------------------- |
| `validate()`                               | Validates all registered objects/bindings |
| `reset()`                                  | Clears all validation results             |
| `addObject(object, rules?)`                | Registers object for validation           |
| `removeObject(object)`                     | Removes object from validation            |
| `addRenderer(renderer)`                    | Adds custom error renderer                |
| `removeRenderer(renderer)`                 | Removes error renderer                    |
| `addError(message, object, propertyName?)` | Adds manual error                         |
| `removeError(result)`                      | Removes specific error                    |

#### ValidateResult

Properties available in validation results:

| Property       | Type    | Description                    |
| -------------- | ------- | ------------------------------ |
| `valid`        | boolean | Validation result              |
| `message`      | string  | Error message                  |
| `object`       | any     | Validated object               |
| `propertyName` | string  | Validated property             |
| `rule`         | any     | Rule that generated the result |

{% hint style="info" %}
When implementing custom validation rules, always handle null/undefined values appropriately to maintain consistent validation behavior.
{% endhint %}

{% hint style="warning" %}
Be careful when using async custom rules, which may impact form submission timing and user experience.
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://aurelia-1.gitbook.io/v1-docs/aurelia-packages/validation.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
