Form API

Drupal's Form API defines each form as one PHP class and supplies the shared parts: CSRF tokens, required-field checks and alter hooks for other modules.

Learn DrupalDrupal glossary

The Form API is the part of Drupal core that builds, validates and processes HTML forms. A developer describes a form as a PHP class that returns a nested array; Drupal turns the array into markup, checks the submitted values and calls the form's submit handler. Core's Basic site settings page is one such form, and so are the forms in custom modules.

How the Form API works in Drupal 11

A form is a class that implements Drupal\Core\Form\FormInterface. The interface has four methods: getFormId(), buildForm(), validateForm() and submitForm(). FormBase is the most commonly used base class, and in it validation is optional. ConfigFormBase is for settings pages and ConfirmFormBase is for confirmation steps.

The $form array is a render array. Keys that start with # are properties such as #type, #title and #required; other keys are child elements. The Render API turns it into HTML.

You show a form by pointing a route at it with the _form key, or by calling \Drupal::formBuilder()->getForm(). Extra arguments to getForm() are passed on to buildForm().

The FormBuilder service runs the process. It calls buildForm(), adds the hidden form_build_id and form_id fields, and runs the alter hooks. On submission it validates required elements, runs each element's #element_validate callbacks, then the form-level #validate handlers. If no errors were set, the #submit handlers run. When no redirect is set, Drupal redirects back to the current path with a 303 status. A handler that calls $form_state->setRebuild() keeps the user on the form, which is how multistep forms work.

Forms shown to authenticated users carry a token tied to the session, a protection against cross-site request forgery. Core skips the token for anonymous users and for forms that use GET.

Example

A route in my_module.routing.yml points to the form class:

my_module.subscribe:
          path: '/subscribe'
          defaults:
            _form: '\Drupal\my_module\Form\SubscribeForm'
            _title: 'Subscribe'
          requirements:
            _permission: 'access content'
<?php

        namespace Drupal\my_module\Form;

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

        class SubscribeForm extends FormBase {

          public function getFormId(): string {
            return 'my_module_subscribe';
          }

          public function buildForm(array $form, FormStateInterface $form_state): array {
            $form['email'] = [
              '#type' => 'email',
              '#title' => $this->t('Email address'),
              '#required' => TRUE,
            ];
            $form['submit'] = [
              '#type' => 'submit',
              '#value' => $this->t('Subscribe'),
            ];
            return $form;
          }

          public function submitForm(array &$form, FormStateInterface $form_state): void {
            $this->messenger()->addStatus($this->t('Thanks, @mail.', ['@mail' => $form_state->getValue('email')]));
          }

        }

Common mistakes

  • Using Drupal 7 habits. drupal_get_form() no longer exists. The FormBuilder service replaced it.
  • Leaving out #title. The validator builds its "field is required" message from the title. When a visible title is unwanted, the validator code recommends setting #title anyway and hiding it with #title_display set to invisible.
  • Altering with the wrong hook. hook_form_alter() runs for every form. For one form, hook_form_FORM_ID_alter() avoids a long list of if checks on the form ID.

For marketing and content teams

When a developer says a form needs a different button label, an extra rule or a hidden field, that is Form API work done in a module, not a setting you can change in the interface. For a settings page, such as Configuration › System › Basic site settings, the form class is ConfigFormBase, so the saved values are configuration that can be exported and deployed.

Sources

Questions about Form API

FormBase is the general base class for a form. ConfigFormBase extends it for settings pages. In 11.2 it adds a Save configuration button, fills elements that have a #config_target property from configuration and writes their values back when the form is submitted.

Implement hook_form_alter(), or the narrower hook_form_FORM_ID_alter(), in a custom module. The form ID is the string the form class returns from getFormId(); it is also submitted in a hidden form_id field of the form.

That message is the error Drupal sets when the form token submitted with an authenticated user's form does not validate against the current session. Drupal then stops validation before any handler runs. The message itself advises copying any unsaved work and reloading the page.

Related terms

All Drupal terms from A to Z