Adding fields (text, select, checkbox) to formsfor Drupal 8 , 9 , 10 , and 11

Last updated :  

A form with just one text field is a clipboard with a single blank line. Real forms need a whole toolbox of input types — free text, a menu of choices, a yes/no toggle — each suited to a different kind of answer. Drupal's Form API ships that entire toolbox as a fixed vocabulary of #type values, so you never hand-write an <input> or <select> tag yourself.

What you'll learn in this lesson

  • The most common Form API element types and when to reach for each one
  • How #options drives select, radios, and checkboxes elements
  • The difference between checkbox (one boolean) and checkboxes (a set of independent choices)
  • Common properties every element accepts, regardless of type

A form with a real mix of element types

$form['username'] = [
  '#type' => 'textfield',
  '#title' => $this->t('Username'),
  '#maxlength' => 60,
  '#required' => TRUE,
];

$form['bio'] = [
  '#type' => 'textarea',
  '#title' => $this->t('Short bio'),
  '#rows' => 4,
];

$form['role'] = [
  '#type' => 'select',
  '#title' => $this->t('Role'),
  '#options' => [
    'editor' => $this->t('Editor'),
    'reviewer' => $this->t('Reviewer'),
    'admin' => $this->t('Administrator'),
  ],
  '#default_value' => 'editor',
];

$form['newsletter'] = [
  '#type' => 'checkbox',
  '#title' => $this->t('Subscribe to the newsletter'),
];

$form['interests'] = [
  '#type' => 'checkboxes',
  '#title' => $this->t('Areas of interest'),
  '#options' => [
    'php' => $this->t('PHP'),
    'js' => $this->t('JavaScript'),
    'design' => $this->t('Design'),
  ],
];

#type: the one property every element must have

#type is what tells Drupal's Form API renderer which widget to draw and which value format to expect back. textfield and textarea both collect a plain string; select, radios, and checkboxes all rely on an #options array mapping a machine-readable key to a human-readable label — the key is what you get back in $form_state, the label is only ever what the visitor sees.

checkbox vs. checkboxes — a naming trap

These look like the same word with an "s," but they're structurally different. A single checkbox element has no #options at all — it's just one boolean, on or off, and $form_state->getValue('newsletter') returns 1 or 0. A checkboxes element (plural) needs #options, renders one checkbox per option, and returns an associative array where each selected key maps to itself and each unselected key maps to 0 — not simply the list of what was checked.

Common mistake: treating checkboxes' return value like a clean array of selected values. It isn't — you almost always need array_filter($form_state->getValue('interests')) to strip out the unchecked 0 entries before the result is actually useful.

Properties every element type shares

  • #title — the visible label
  • #description — helper text rendered below the element
  • #required — adds both a red asterisk and server-side validation that rejects an empty submission
  • #default_value — pre-fills the element (matches a key in #options for select/radios/checkboxes)
  • #disabled — renders the element but blocks editing and value submission

Quick check: you add a checkboxes element with three options but the visitor only ticks one. What does $form_state->getValue() return for that field — a single string, or something else? It returns an array with all three keys present: the ticked one mapped to itself, the other two mapped to 0. That's exactly why array_filter() is the standard next step.

Key takeaways

  • Every Form API element needs a #type — it determines both the rendered widget and the shape of the value you get back.
  • select, radios, and checkboxes all use an #options array of machine-key => human-label pairs.
  • checkbox (singular) is one boolean; checkboxes (plural) is a set, and its return value needs array_filter() to be useful.
  • #required, #description, #default_value, and #disabled work the same way across nearly every element type.

Coming up next

Elements collect input — but nothing stops a visitor from submitting garbage. Next, we add real validation: rejecting bad input before it ever reaches your submit handler.