Skip to content

Architecture: Core Form Classes

When to Use

Choose the appropriate base class based on your form's purpose. Use FormBase for general forms, ConfigFormBase for settings, ConfirmFormBase for confirmations.

Reference: Primary Interfaces

FormInterface - Base form contract

  • Location: /web/core/lib/Drupal/Core/Form/FormInterface.php
  • Methods: getFormId(), buildForm(), validateForm(), submitForm()
  • When to implement: Custom form without base class benefits

FormStateInterface - State management contract

  • Location: /web/core/lib/Drupal/Core/Form/FormStateInterface.php
  • Size: 1160+ lines defining all state operations
  • Purpose: Value access, storage, control flags, error handling

FormBuilderInterface - Form building service contract

  • Location: /web/core/lib/Drupal/Core/Form/FormBuilderInterface.php
  • Service: @form_builder
  • Use for: Programmatic form rendering, submission

Reference: Base Form Classes

Class Purpose Location
FormBase Standard forms with DI /web/core/lib/Drupal/Core/Form/FormBase.php
ConfigFormBase Config management /web/core/lib/Drupal/Core/Form/ConfigFormBase.php
ConfirmFormBase Confirmation dialogs /web/core/lib/Drupal/Core/Form/ConfirmFormBase.php

Reference: Core Services

Service Purpose File
FormBuilder Main building engine /web/core/lib/Drupal/Core/Form/FormBuilder.php
FormValidator Validation orchestration /web/core/lib/Drupal/Core/Form/FormValidator.php
FormSubmitter Submission handling /web/core/lib/Drupal/Core/Form/FormSubmitter.php

Decision: Base Class Selection

Need config management? → ConfigFormBase
Need confirmation dialog? → ConfirmFormBase
Entity create/edit? → EntityForm (see Entity API guide)
Everything else? → FormBase

Pattern: Extending a Base Class

All forms implement FormInterface or extend a base class.

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;

class MyForm extends FormBase {
  public function getFormId() {
    return 'my_module_form';
  }

  public function buildForm(array $form, FormStateInterface $form_state) {
    // Define form structure
    return $form;
  }

  public function submitForm(array &$form, FormStateInterface $form_state) {
    // Process submission
  }
}

Common Mistakes

  • Not using dependency injection via create() method
    • WHY BAD: Breaks unit testing (can't mock services), violates SOLID principles, prevents service substitution for testing/overrides
  • Extending wrong base class (ConfigFormBase for non-config forms)
    • WHY BAD: ConfigFormBase expects config schema, requires getEditableConfigNames(), adds unnecessary overhead for non-config data
  • Implementing FormInterface directly when base class would work
    • WHY BAD: Lose helper methods (t(), messenger(), config(), etc.), must implement all interface methods manually, harder to maintain

See Also