Every AJAX example so far has assumed the happy path: the request goes out, the server responds, commands run, everyone's happy. Real sites hit network drops, server exceptions, and validation failures constantly — and an AJAX interaction that fails silently, leaving a spinner frozen or nothing visibly happening, is one of the most frustrating experiences you can hand a visitor.
What you'll learn in this lesson
- What happens by default when an AJAX callback throws an exception
- Surfacing a friendly error via
MessageCommandinstead of a blank failure - Validation errors inside AJAX-driven forms specifically
- Client-side error handling for genuine network failures
The default behavior when a callback throws
If an AJAX callback throws an uncaught exception, Drupal's normal exception-handling pipeline still runs — the response comes back as an error page's HTML wrapped inside what the AJAX system expected to be a clean command list. The result, from the visitor's point of view, is usually a broken-looking page fragment or nothing updating at all, with the real error only visible in the server's logs. That's the core problem this lesson solves: turning an invisible server-side failure into something the visitor actually sees and can act on.
Catching the failure and reporting it as a command
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\MessageCommand;
public function ajaxCallbackThatMightFail(array $form, FormStateInterface $form_state) {
$response = new AjaxResponse();
try {
$result = $this->riskyOperation();
$response->addCommand(new ReplaceCommand('#result-wrapper', $this->buildResultMarkup($result)));
}
catch (\Exception $e) {
$this->getLogger('my_module')->error('AJAX callback failed: @message', ['@message' => $e->getMessage()]);
$response->addCommand(new MessageCommand(
$this->t('Something went wrong processing your request. Please try again.'),
NULL,
['type' => 'error']
));
}
return $response;
}
The shape here matters: catch the exception yourself, log the real technical detail for whoever reads the logs later, and return a generic, visitor-facing message via MessageCommand — never the raw exception text, which can leak internal implementation details and is rarely meaningful to an end user anyway.
Validation errors inside AJAX-driven forms
When the AJAX-triggering element lives inside a form with its own validateForm(), Drupal's Form API already has a well-established path for this: call $form_state->setErrorByName() during validation exactly as you would for a normal submission, and let the AJAX callback simply return the relevant piece of the rebuilt $form array (the pattern from earlier AJAX lessons). Drupal automatically renders any set errors as part of that rebuilt markup — there's no separate "AJAX validation error" mechanism to learn, it's the same validation system, just delivered without a full page reload.
catch (\Exception $e) {} with nothing inside is the single most common way an AJAX failure becomes genuinely invisible — no error in the log, no message to the visitor, nothing. Always do at least one of: log it, or surface a message. Ideally both.Genuine network failures — the client side of the story
Everything above assumes the request reached your server at all. A dropped connection or a timeout never gets to your PHP code — that's handled entirely client-side by Drupal's own AJAX JavaScript, which shows a generic "An AJAX HTTP error occurred" message by default. You can customize this globally via Drupal.behaviors and the ajaxError jQuery event if a project genuinely needs different client-side error copy, but for most modules the built-in fallback is sufficient — it's the server-side failures (the ones inside your own PHP) that are worth deliberate handling, since those are the ones your code actually controls.
Quick check: your AJAX callback's
tryblock succeeds, but you also want to show a friendly "Saved!" confirmation alongside the real content update. Do you need a second AJAX request for that? If you said no — add a secondMessageCommandto the same response alongside yourReplaceCommand, exactly like combining any two commands from the previous lesson. Success feedback and failure feedback use the identical mechanism.
Key takeaways
- An uncaught exception in an AJAX callback produces a broken-looking response, not a clean error — always wrap risky logic in your own try/catch.
- Log the real exception message server-side; show a generic, non-technical message to the visitor via
MessageCommand. - AJAX-driven form validation reuses the exact same
setErrorByName()mechanism as normal form validation — no separate system to learn. - Genuine network-level failures (dropped connections, timeouts) never reach your PHP at all — they're handled by Drupal's client-side AJAX JavaScript before your callback is ever called.
Coming up next
You've now covered the AJAX framework end to end — callbacks, commands, and failure handling. Next, we shift to a different kind of visibility entirely: logging, and how to make sure you (and future you) can actually tell what your module did after the fact.