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
#optionsdrives select, radios, and checkboxes elements - The difference between
checkbox(one boolean) andcheckboxes(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.
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#optionsfor select/radios/checkboxes)#disabled— renders the element but blocks editing and value submission
Quick check: you add a
checkboxeselement 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 to0. That's exactly whyarray_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, andcheckboxesall use an#optionsarray of machine-key => human-label pairs.checkbox(singular) is one boolean;checkboxes(plural) is a set, and its return value needsarray_filter()to be useful.#required,#description,#default_value, and#disabledwork 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.