You've built forms that submit, validate, and store data — but every one of them so far has needed a full page reload to do anything. Think of a restaurant order pad versus a live kitchen display: the order pad only updates once, when the waiter walks it over; the kitchen display updates the instant a new ticket prints, with nobody having to physically carry paper anywhere. Drupal's #ajax property turns any form element into that live display — a change on the page triggers a server round-trip and a targeted update, with no full reload and, crucially, no hand-written JavaScript required.
What you'll learn in this lesson
- The two keys every
#ajaxarray needs —callbackandwrapper— and what each one actually does - Why the AJAX callback method itself does no real work
- How a wrapper element's HTML
idis what gets replaced on the page - How a dependent-select pattern (one field's choice determining another's options) falls naturally out of this mechanism
The two pieces of an #ajax array
$form['temperature'] = [
'#title' => $this->t('Temperature'),
'#type' => 'select',
'#options' => $this->getColorTemperatures(),
'#empty_option' => $this->t('- Select a color temperature -'),
'#ajax' => [
'callback' => '::updateColor',
'wrapper' => 'color-wrapper',
],
];
Add an #ajax array to almost any form element and Drupal attaches JavaScript behind the scenes that intercepts its change or click event, sends an asynchronous request, and waits for a response — you never write that JavaScript yourself. Two keys matter:
callback— the PHP method Drupal invokes server-side once the request arrives. The::prefix means "a method on this form class."wrapper— the HTMLidof an element already present on the page, whose inner content gets replaced with whatever the callback returns.
Building the wrapper element
$form['color_wrapper'] = [
'#type' => 'container',
'#attributes' => ['id' => 'color-wrapper'],
];
$temperature = $form_state->getValue('temperature');
if (!empty($temperature)) {
$form['color_wrapper']['color'] = [
'#type' => 'select',
'#title' => $this->t('Color'),
'#options' => $this->getColorsByTemperature($temperature),
];
}
This is the detail that trips people up the first time they meet this pattern: the color dropdown isn't specially wired for AJAX at all. It's an ordinary conditional — buildForm() checks whether a temperature has been selected yet, and only adds the color select if one has. The moment someone picks a temperature, Drupal rebuilds the whole form server-side with that value already present in $form_state, and this same conditional now evaluates true.
buildForm() by the time the callback runs. Its only job is to hand back the already-correct slice of the rebuilt $form array so Drupal can render it and swap it into the wrapper:
public function updateColor(array $form, FormStateInterface $form_state) {
return $form['color_wrapper'];
}
That's the entire method — one line. Every AJAX callback you write will tend to look this thin once you internalize this pattern.Quick check: why doesn't
updateColor()need a parameter telling it which temperature was picked? (Answer: becausebuildForm()already read$form_state->getValue('temperature')and built the correct color options before the callback ever ran — the callback just returns what's already sitting in$form.)
Key takeaways
- The
#ajaxproperty turns any element's change or click event into an asynchronous request — Drupal generates all the JavaScript; you only supply acallbackand awrapper. - Wrap the target region in an element with a stable
id, either acontainerwith#attributes => ['id' => '...']or#prefix/#suffixon any element. - AJAX callbacks should never contain real logic — they only return the slice of the already-rebuilt
$formarray that should replace the wrapper's contents. - A dependent-select pattern (one field's value determining another field's options) falls out naturally from an ordinary conditional in
buildForm()— no special AJAX-only code path needed.
Coming up next
You've seen the shape of an AJAX callback, but glossed over what "return the wrapper" actually means under the hood. The next lesson opens that up properly — the exact signature an AJAX callback function needs, and what Drupal does with whatever you hand it back.