Every AJAX example so far assumed the happy path: the request goes out, the server responds, the browser updates. Real forms fail sometimes — a validation error, a timeout, a server exception — and an AJAX request that silently does nothing when that happens is worse than no AJAX at all, because the visitor gets zero feedback that anything went wrong. This lesson covers what actually happens on failure, and how to make sure your visitor finds out.
What you'll learn in this lesson
- What happens by default when an AJAX-triggered validation fails
- How to surface validation errors through the same wrapper mechanism you already know
- What a genuine server-side failure looks like to the browser, and Drupal's default handling
- A defensive pattern for AJAX callbacks that might throw
Validation errors during an AJAX request
If an AJAX-triggered element has its own #element_validate or the form's validateForm() sets an error, Drupal still runs the normal validation pipeline before your AJAX callback fires. The difference from a full-page submission: nothing redirects, and by default the error messages are attached to the rebuilt form rather than shown as a separate page-top message list. The safest, most explicit pattern is to make sure your callback's wrapper actually contains the element the error is attached to, so Drupal's own error-rendering shows up inside the region that just got swapped in:
public function myCallback(array &$form, FormStateInterface $form_state) {
// Returning the parent element (not just a leaf field) ensures any
// #description or inline error markup attached during validation is
// included in what gets swapped into the wrapper.
return $form['my_wrapper'];
}
Catching real exceptions inside a callback
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\HtmlCommand;
public function myCallback(array &$form, FormStateInterface $form_state) {
$response = new AjaxResponse();
try {
$result = $this->doSomethingThatMightFail();
$response->addCommand(new HtmlCommand('#result-wrapper', $result));
}
catch (\Exception $e) {
$this->getLogger('my_module')->error($e->getMessage());
$response->addCommand(new HtmlCommand(
'#result-wrapper',
'<div class="messages messages--error">' . $this->t('Something went wrong. Please try again.') . '</div>'
));
}
return $response;
}
An uncaught exception inside an AJAX callback doesn't crash the visible page the way it would on a normal request — the AJAX request itself fails, Drupal's own JavaScript shows a generic "An error occurred" message, and the visitor is left staring at a form that silently didn't do what they expected. Wrapping genuinely risky logic in a try/catch and returning your own honest error markup, styled with core's own messages--error class, gives the visitor a real explanation instead of a vague default.
Quick check: your AJAX callback calls an external API that occasionally times out, and you haven't wrapped the call in a try/catch. What does the visitor actually see? (Answer: Drupal's default JavaScript error handling kicks in — usually a generic "An AJAX HTTP error occurred" message — rather than anything specific to what actually failed, because the uncaught exception never reaches your own error-formatting code.)
Key takeaways
- Validation errors during an AJAX request still run through the normal validation pipeline — make sure your callback's wrapper includes the element the error is attached to.
- An uncaught exception inside a callback triggers Drupal's generic default error handling, not a helpful message.
- Wrap genuinely risky logic (external calls, file operations) in try/catch and return your own styled error markup via
HtmlCommand. - Always log the real exception even when you show the visitor a friendlier, generic message.
Coming up next
You've now covered the mechanics and the failure modes of AJAX callbacks individually. The next lesson puts several of these pieces together into a real dependent-field chain — one selection cascading through two or three connected fields.