Skip to content

Form API

Most custom forms in Drupal extend the FormBase class.

There are three main types of forms:

  • A generic form: extend FormBase
  • A configuration form that enables administrators to update a module's settings: extend ConfigFormBase
  • A form for deleting content or configuration, which provides a confirmation step: extend ConfirmFormBase

The latter two extend FormBase under the hood, but they have different sets of required methods when extended.

Required Methods When Extending FormBase

  • getFormId()

Should return a string with a unique ID for your form.

  • buildForm()
php
public function buildForm(array $form, FormStateInterface $form_state) {}

Add any form elements to the $form array. Should return the $form variable.

  • validateForm()
php
public function validateForm(array &$form, FormStateInterface $form_state) {}

Access a specific form value using $form_state->getValue('field_id') or all values using $form_state->getValues().

To set a validation error, use the $form_state variable like this:

php
$form_state->setError($form['phone_number'], $this->t('The phone number is too short. Please enter a full phone number.'));
$form_state->setErrorByName('phone_number', $this->t('The phone number is too short. Please enter a full phone number.'));
  • submitForm()
php
public function submitForm(array &$form, FormStateInterface $form_state) {}

You can set a redirect here using $form_state->setRedirect().

Required Methods When Extending ConfigFormBase

  • getFormId()
  • buildForm()
  • getEditableConfigNames()
php
protected function getEditableConfigNames() {}

Should return an array of config name strings.

  • submitForm()

You should save values to the config here.

The ConfigFormBase class has a default implementation, but you can override it if needed.

Required Methods When Extending ConfirmFormBase

  • getFormId()
  • submitForm()
  • getCancelUrl()

Should return a Url object.

  • getQuestion()

Should return a string or translatable string.

Form Page

Create the my_module.routing.yml file:

yml
example.form:
  path: '/example-form'
  defaults:
    _title: 'Example form'
    _form: '\Drupal\my_module\Form\ExampleForm'
  requirements:
    _permission: 'access content'

Retrieving the Form Programmatically

You may need to render a form inside a custom block, for example:

php
$form = \Drupal::formBuilder()->getForm('Drupal\example\Form\ExampleForm', $para);
// OR
$form_object = new \Drupal\mymodule\Form\ExampleForm($something_special);
$form_builder->getForm($form_object);

Altering a Form

You can alter existing forms or your custom forms using hooks:

  • hook_form_alter()
  • hook_form_FORM_ID_alter()

Example usage:

php
function my_module_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
  $form['example']['#title'] = t('Test');
}

Form Elements

Form elements are HTML elements represented as render arrays. You can set up form elements inside the buildForm() method of your form class or in a form alter hook.

Sample form element definition:

php
$form['element'] = [
  '#type' => 'textfield',
  '#title' => $this->t('Element'),
];

Each form element accepts general properties, but different form element types may also have unique properties specific to them.

General Properties:

  • #title (string) The title of the form element. Should be translated.
  • #description (string) Help or description text for the element. In an ideal user interface, the #title should be enough to describe the element, so most elements should not have a description. If you do need one, make sure it is translated. If it is not already wrapped in a safe markup object, it will be filtered for XSS safety.
  • #default_value The default value for the element. See also #value.
  • #required (bool) Whether or not input is required for the element.
  • #access (bool) Whether the element is accessible or not. When FALSE, the element is not rendered, and the user-submitted value is not considered.
  • #disabled (bool) If TRUE, the element is shown but does not accept user input.
  • #ajax (array) An array specifying Ajax behavior. See the JavaScript API and AJAX Forms guides for more information.
  • #prefix (string) A prefix to display before the HTML input element. Should be translated. If it is not already wrapped in a safe markup object, it will be filtered for XSS safety.
  • #suffix (string) A suffix to display after the HTML input element. Should be translated. If it is not already wrapped in a safe markup object, it will be filtered for XSS safety.
  • #required_error (string) Override the default error message. "@field_title is required" will be used if this is undefined.
  • #states (array) Information about JavaScript states, such as when to hide or show the element based on input from other elements. Refer to FormHelper::processStates.
  • #title_display (string) Where and how to display the #title. Possible values: before, after, invisible, attribute (for a hover tooltip).
  • #tree (bool) TRUE if the values of this element and its children should be hierarchical in $form_state. FALSE if the values should be flat. See also #parents, #array_parents.
  • #value Used to set values that cannot be edited by the user. This should NOT be confused with #default_value, which is for form inputs where users can override the default value. Used by elements like button, hidden, image_button, submit, token, and value.

See all

Common Form Element Types:

  • textfield
  • select
  • checkbox / checkboxes
  • radios
  • textarea
  • number
  • tel
  • email
  • date
  • url
  • range
  • entity_autocomplete (accepts unique properties):
php
$form['reference_nodes'] = [
  '#target_type' => 'node',
  '#tags' => TRUE,
  '#default_value' => $node,
  '#selection_handler' => 'default',
  '#selection_settings' => [
    'target_bundles' => ['article', 'page'],
  ],
  '#autocreate' => [
    'bundle' => 'article',
    'uid' => <a valid user ID>,
  ],
];

See all

Aside from Form Elements, there are also Render Elements as part of Drupal's Render API. These include types like:

Conditional Form Fields with #states

Drupal's Form API #states property allows you to easily show, hide, enable, disable, require, or collapse form fields based on values selected or entered in other fields on the form or elsewhere on the page.

States that can be applied to a form field element:

  • enabled
  • disabled
  • required
  • optional
  • visible
  • invisible
  • checked
  • unchecked
  • expanded
  • collapsed

The following states may be used when checking values of other fields:

  • empty
  • filled
  • checked
  • unchecked
  • expanded
  • collapsed
  • value

Examples:

php
# 1
'#states' => [
   // Show this text field only if the 'other' radio button is selected above.
   'visible' => [
      // Don't mistake :input for the type of field or a CSS selector --
      // it's a jQuery selector.
      // You can always use :input or any other jQuery selector here, no matter
      // whether your source is a select, radio, or checkbox element.
      // In the case of radio buttons, we can select them by their name instead of their ID.
      ':input[name="colour_select"]' => ['value' => 'other'],
   ],
],

# 2
'#states' => [
   // Show this text field if either the 'other' or 'custom' radio button is selected.
   'visible' => [
      ':input[name="colour_select"]' => [
          // User selected 'Other'.
          ['value' => 'other'],
          // Conditional logic. Allowed values are or, xor, and.
          'or',
          // User selected 'Custom color'.
          ['value' => 'custom'],
      ],
   ],
],

# 3
'#states' => [
   'visible' => [
     ':input[name="field_override_subject"]' => ['checked' => TRUE],
   ],
],

# 4
$form['example_field']['#states'] = [
   'visible' => [
      [':input[name="my_select_list"]' => ['value' => 'user']],
   ],
];

# 5 Node form field
$form['field_some_checkbox']['widget'][0]['value']['#states'] = [
   'unchecked' => ['select[name="field_some_select"]' => ['value' => 'a-specific-value']],
];

#process and #after_build

php
$form['#process'][] = 'yourmodulename_form_process';

Can be used on a form but is more often added to a form element. It is used to alter how an element works or looks, often breaking complex elements into smaller parts (e.g., turning a #radios element into multiple #radio elements). This is mostly used when you’ve created a custom form element.

php
$form['#after_build'][] = 'yourmodulename_after_build';

Use this when you need to alter the form after all other form alters have been processed.

AJAX

The #ajax property of a form element is an array. Here are the details of its known elements, all of which are optional:

  • callback The callback to handle the server-side Ajax event. More information on callbacks is under "Setting up a callback to process Ajax".

If you use callback, your function will receive the $form and $form_state from the triggering form. You can use $form_state to access data the user entered into the form.

  • wrapper The HTML id attribute of the area where the content returned by the callback should be placed. Callbacks can return content or JavaScript commands; the wrapper is used for content returns.
  • method The jQuery method for placing new content (used with wrapper). Valid options are 'replaceWith' (default), 'append', 'prepend', 'before', 'after', or 'html'. See http://api.jquery.com/category/manipulation/ for more on these methods.
  • effect The jQuery effect to use when placing the new HTML (used with wrapper). Valid options are 'none' (default), 'slide', or 'fade'.
  • speed The effect speed to use (with effect and wrapper). Valid options are 'slow' (default), 'fast', or a specific number of milliseconds.
  • event The JavaScript event to respond to. This is selected automatically for the type of form element, but you can override it. For example, the event for submit, button, and image_button elements is "mousedown" (not "click").
  • prevent A JavaScript event to prevent when the event is triggered. For example, if you use 'mousedown' on a button, you might want to prevent 'click' events from being triggered.
  • disable-refocus Disable automatic refocusing after an Ajax call.
  • progress An array indicating how to show Ajax processing progress. It can contain:
    • type Type of indicator, either 'throbber' (default), 'bar', or 'none'.
    • message Translated message to display.
    • url For a bar progress indicator, the URL path for determining progress.
    • interval For a bar progress indicator, how often to update it.
  • url A \Drupal\Core\Url to which the Ajax request should be submitted. Defaults to the form or link's URL (or a slightly modified version). It is recommended to omit this key and rely on Drupal’s content negotiation.

Example AJAX callback functions:

php
# Example 1: Open modal dialog via AJAX.
public function ajaxCallback(array &$form, FormStateInterface $form_state) {
   $response = new AjaxResponse();
   $response->addCommand(new OpenModalDialogCommand(
      $this->t('Need help?'),
      "Help text",
      ['dialogClass' => 'test-popup', 'width' => 500]
   ));
   return $response;
}

# Example 2: Replace form with an updated version, and display messages if any exist.
public function ajaxCallback(array &$form, FormStateInterface $form_state) {
   return $form;
}

Dependency Injection in Form Class

In PHP 8, dependency injection is simplified. Add these methods to your form class and include the desired services:

php
/**
 * Constructs a new CustomForm object.
 */
public function __construct(
  protected ExampleServiceInterface $exampleService,
  protected EntityTypeManagerInterface $entityTypeManager
) {}

/**
 * {@inheritdoc}
 */
public static function create(ContainerInterface $container) {
  return new static(
    $container->get('example_service'),
    $container->get('entity_type.manager')
  );
}

Note that extending FormBase gives you access to some predefined methods, so there is no need to inject them yourself:

  • $this->currentUser()
  • $this->messenger()
  • $this->logger('channel')
  • $this->configFactory()

Questions

Which parameters does hook_form_alter() accept?
php
hook_form_alter(&$form, FormStateInterface $form_state, $form_id);
How to add an additional validate function using hook_form_alter()?
php
$form['#validate'][] = 'function_name'; // Will be called last.
How to add an additional validate function using hook_form_alter()
php
$form['actions']['submit']['#submit'][] = 'function_name'; // Will be called last.
// This can work in some cases:
$form['#submit'][] = 'function_name'; // Will be called last.
How to create a render array for an autocomplete of category taxonomy terms?
php
$form['category'] = [
   '#type' => 'entity_autocomplete',
   '#title' => t('Category'),
   '#target_type' => 'taxonomy_term',
   '#selection_settings' => [
      'target_bundles' => ['category'],
   ],
];
How do you automatically hide the "name" field when the "anonymous" checkbox is checked?
php
// We need to use #states
$form['name']['#states'] = [
   'invisible' => [
      ':input[name="anonymous"]' => ['checked' => TRUE],
   ],
];

Resources