Using buildForm() to define form elementsfor Drupal 11 , 10 , 9 , and 8

Last updated :  

The previous lesson's buildForm() returned an empty array. This lesson fills it in — the actual work of describing a form: what fields exist, what they're called, what type they are. Every key you add to that returned array is one more piece of the form Drupal will render.

What you'll learn in this lesson

  • How array keys become field machine names
  • The #type property, and why it's the one every element needs
  • Reading submitted values back out via the array key

Building the form array

public function buildForm(array $form, FormStateInterface $form_state) {
  $form['name'] = [
    '#type' => 'textfield',
    '#title' => $this->t('Your name'),
    '#required' => TRUE,
  ];

  $form['email'] = [
    '#type' => 'email',
    '#title' => $this->t('Your email'),
  ];

  $form['actions'] = [
    '#type' => 'actions',
  ];
  $form['actions']['submit'] = [
    '#type' => 'submit',
    '#value' => $this->t('Send'),
  ];

  return $form;
}

Each top-level key (name, email, actions) becomes that element's machine name — this is exactly what you'll later use to read the submitted value back out with $form_state->getValue('name'). The key you choose here isn't cosmetic; it's the identifier for that field everywhere else in your form class.

Why actions wraps the submit button

Wrapping the submit button in an actions container (itself a render element of #type => 'actions') is a Drupal convention, not a strict requirement — it lets themes consistently style the button area separately from the fields above it, and lets multiple buttons (Submit, Cancel, Save Draft) sit together in one predictable place.

Every element needs a #type. Without it, Drupal has no idea what to render — #type => 'textfield' versus 'email' versus 'select' is what determines the actual HTML input element, its validation behavior (an email type gets automatic format validation you didn't write), and which other #-properties are even meaningful for that element.

Reading values back

public function submitForm(array &$form, FormStateInterface $form_state) {
  $name = $form_state->getValue('name');
  $email = $form_state->getValue('email');
  // The string 'name' here must match the array key from buildForm() exactly.
}

Quick check: you rename the array key from 'name' to 'full_name' in buildForm() but forget to update submitForm()'s getValue('name') call. What happens? If you said getValue('name') returns NULL since that key no longer exists in the submitted values, you've understood exactly why the array key is a real identifier connecting buildForm() and submitForm(), not just a label.

Key takeaways

  • Each top-level array key in buildForm()'s return value becomes that field's machine name, used later to read its value.
  • Every form element needs a #type — it determines the rendered HTML, built-in validation, and which other properties apply.
  • Wrapping buttons in an actions container is a theming convention, letting themes style the button area consistently.
  • Field keys in buildForm() and getValue() calls in submitForm() must match exactly — a rename in one without the other silently breaks.

Coming up next

You've built a form's structure. Next, we look at the service responsible for actually turning that array into HTML and processing it — the form builder.