Validating Entity Formsfor Drupal 11 , 10 , 9 , and 8

Last updated :  

Field widgets already reject the obvious problems — a required text field left empty, a number field given letters. Entity-level validation is for everything else: rules that depend on the entity as a whole, or on data outside any single field, like uniqueness checks or cross-field consistency.

What you'll learn in this lesson

  • Where entity-level validation actually runs, relative to field-level validation
  • How to override validateForm() on a ContentEntityForm subclass
  • The difference between validating a single field and validating the entity as a whole
  • How constraint-based validation (via the Entity Validation API) differs from form-level checks

Overriding validateForm() on an entity form

public function validateForm(array &$form, FormStateInterface $form_state) {
  parent::validateForm($form, $form_state);

  $name = $form_state->getValue(['name', 0, 'value']);
  $existing = \Drupal::entityTypeManager()
    ->getStorage('content_entity_example_contact')
    ->loadByProperties(['name' => $name]);

  $current_id = $this->entity->id();
  foreach ($existing as $entity) {
    if ($entity->id() != $current_id) {
      $form_state->setErrorByName('name', $this->t('A contact with this name already exists.'));
      break;
    }
  }
}

Calling parent::validateForm() first matters: the base class runs each field widget's own validation (required checks, type checks, any #element_validate callbacks) before your custom logic runs. Skip that call and you silently lose all of the field-level validation the form was already doing for free.

The uniqueness check itself is a genuinely entity-level concern — no single field widget can know "does another entity already have this value," because that requires querying storage, not just inspecting the submitted value in isolation.

Excluding the entity being edited matters: the $entity->id() != $current_id check above is what stops the edit form from flagging a contact's own unchanged name as a duplicate of itself. Forget this and every edit of an existing, unmodified contact starts failing validation the moment you save it.

The other path: entity constraints

Drupal also has a separate, lower-level Entity Validation API (\Drupal\Core\Entity\EntityConstraintViolationListInterface, driven by Symfony Validator constraints attached to field definitions). Form-level validateForm() overrides, like the one above, are the more common and more direct approach for form-specific business rules; entity constraints matter more when you need the same rule enforced even for entities saved outside any form — a migration script or a REST API write, for instance.

See it for yourself

If you have content_entity_example enabled, create two contacts with the same name via the add form and observe Drupal's default behavior, then compare it against what a uniqueness override like the one above would add.

Quick check: why does the code above compare against $this->entity->id() rather than just checking "does any contact have this name"? (Because on the edit form, the entity being edited is itself one of the entities that already has that name — without excluding it by ID, saving an untouched existing contact would always trigger the duplicate error.)

Key takeaways

  • Always call parent::validateForm() first when overriding it on an entity form — it runs all the field-level validation you'd otherwise lose.
  • Entity-level validation is for rules that need more than one field's value, or that need to query other entities (like uniqueness).
  • On edit forms, exclude the entity's own ID from any "does this already exist" check, or every unmodified save will fail.
  • The Entity Validation API (constraints) is a separate, lower-level mechanism worth reaching for only when a rule must hold even outside form submissions.

Coming up next

Once a submission passes validation, something has to actually persist it. Next, we look at exactly what happens inside an entity form's submitForm()/save() cycle.