An AjaxResponse is never HTML by itself — it's a list of instructions, and each instruction is a command. Think of it like a stage manager's cue sheet: "replace this prop, append that line, fade this light" — the browser reads the cue sheet and carries out each cue in order. This lesson tours the command system properly.
What you'll learn in this lesson
- What an
AjaxResponseactually contains - The real
AppendCommandpattern from the Examples module - The most common commands you'll reach for, and when each fits
- Why a callback can return multiple commands in one response
A real command, from the Examples module
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\AppendCommand;
public function ajaxLinkCallback($nojs = 'nojs') {
if ($nojs == 'ajax') {
$output = $this->t("This is some content delivered via AJAX");
$response = new AjaxResponse();
$response->addCommand(new AppendCommand('#ajax-example-destination-div', $output));
return $response;
}
// ...
}
AppendCommand('#ajax-example-destination-div', $output) tells the browser: "find the element matching this CSS selector, and append this HTML inside it, after any existing content." addCommand() can be called as many times as you like on one AjaxResponse — each call queues one more instruction, and the browser executes all of them, in the order added, from a single request.
The command family you'll actually use
AppendCommand($selector, $content)— adds content inside an element, after whatever's already there. Good for "load more" style additions.ReplaceCommand($selector, $content)— swaps an element's entire outer HTML for new markup. The most common command for "re-render this whole region with fresh state."HtmlCommand($selector, $content)— replaces only the inner HTML of an element, keeping the element itself (and any attributes on it) intact.InvokeCommand($selector, $method, $args)— calls an arbitrary jQuery method on matched elements, for anything the built-in commands don't cover directly (toggling a class, calling a plugin's method).RemoveCommand($selector)— deletes matched elements from the page entirely.MessageCommand($message)— displays a Drupal status/warning/error message the same way$this->messenger()->addMessage()would on a full page load, but without a page reload.
Combining commands in one response
$response = new AjaxResponse();
$response->addCommand(new ReplaceCommand('#results-wrapper', $new_results_markup));
$response->addCommand(new MessageCommand($this->t('Results updated.')));
return $response;
This is exactly why the command pattern exists rather than an AJAX callback just returning one blob of HTML: a single interaction often needs to update more than one unrelated part of the page — swap the main content and show a confirmation message and, say, update a counter elsewhere — and each of those is naturally its own command targeting its own selector.
'#my-id', '.my-class'), matched against the live DOM exactly the way document.querySelector() would. It's easy to assume some special Drupal-only targeting is happening — there isn't; get the selector wrong and the command silently does nothing, with no error anywhere.Quick check: you need to update a summary count elsewhere on the page and replace a results table, from one AJAX callback triggered by a single button. Do you need two separate AJAX requests? If you said no — one
AjaxResponsewith twoaddCommand()calls (oneReplaceCommandfor the table, one for the count) handles both updates from a single round trip.
Key takeaways
- An
AjaxResponseis a container for one or more commands, executed by the browser in the order they were added. ReplaceCommandandHtmlCommandcover most "update this region" needs; the difference is whether the target element itself is replaced or just its contents.MessageCommandgives you Drupal's normal status-message styling without a page reload.- Command selectors are plain CSS selectors matched against the live DOM — a typo fails silently, with no visible error.
Coming up next
Commands assume everything went well — but AJAX requests fail too (network errors, server exceptions, validation failures). Next: handling those failure cases gracefully instead of leaving the user staring at a request that silently went nowhere.