Skip to content

Validation Architecture

When to Use

Use element-level validation for single-field checks, form-level for cross-field validation, typed config for automatic schema validation.

Reference: Validation Levels

Three-Tier Validation System:

1. Element-level (#element_validate)
   - Per-element basis
   - Format, range, pattern validation
   - Runs first

2. Form-level (validateForm(), #validate)
   - Cross-field validation
   - Business logic
   - Runs second

3. Typed config (ConfigFormBase only)
   - Schema constraint validation
   - Runs automatically from config schema
   - Runs third

Reference Implementation:

  • File: /web/core/lib/Drupal/Core/Form/FormValidator.php
  • Method: validateForm() lines 94-126 shows execution order
  • Method: doValidateForm() lines 122+ recursive validation

Reference: Validation Execution Order

Complete Chain:

1. CSRF token validation (failure stops all processing)
2. Element validators (depth-first tree traversal)
   - #element_validate callbacks
   - Built-in element type validation
3. Form validators (#validate array)
   - In order added (via hook_form_alter or buildForm)
4. Form class validateForm() method
5. Typed config validators (ConfigFormBase only)
   - Constraint validators from schema
6. If any errors: redisplay form, no submit handlers run

Reference: /web/core/lib/Drupal/Core/Form/form.api.php lines 180-237

Reference: Error Setting Methods

By Element Name (Preferred):

$form_state->setErrorByName('field_name', $this->t('Error message'));

By Element Reference:

$form_state->setError($form['field_name'], $this->t('Error message'));

Error Checking:

if ($form_state->hasAnyErrors()) { /* ... */ }
$errors = $form_state->getErrors(); // Associative array
$form_state->clearErrors(); // Reset all

Decision: Validation Patterns

Element Validation:

When: Single field format/range check
Where: #element_validate property
Example: Email format, number range, string length
Pattern: Return NULL, errors via setErrorByName()

Form Validation:

When: Cross-field validation, business logic
Where: validateForm() method or #validate array
Example: End date > start date, unique constraint
Pattern: Access multiple fields via getValue()

Typed Config Validation (Drupal 10.2+):

When: ConfigFormBase with schema constraints
Where: Automatic from mymodule.schema.yml
Example: Min/max values, required fields, data types
Pattern: Define constraints in schema, validation automatic

Reference: Config Validation

Common Mistakes

  • Setting errors in buildForm() (wrong phase)
  • Using element validation for cross-field checks (no access to other fields)
  • Not translating error messages (use $this->t())
  • Forgetting to check for existing errors before setting new ones

See Also