Using AJAX Commands: HtmlCommand, ReplaceCommand, and Morefor Drupal 8 , 9 , 10 , and 11

Last updated :  

Last lesson introduced AppendCommand as one way to tell the browser what to do with an AJAX response. Think of Drupal's AJAX commands as a small, fixed vocabulary the server and browser both agree on in advance — the server never sends raw instructions, it sends one of these named commands, and JavaScript already shipped with Drupal core knows exactly how to carry each one out. This lesson tours the rest of that vocabulary.

What you'll learn in this lesson

  • The most commonly used AJAX commands and what each one actually does to the page
  • How ReplaceCommand differs from HtmlCommand — a distinction that trips people up
  • How to open a real modal dialog from an AJAX callback
  • Why you'll usually only need three or four of these commands in practice

The core commands you'll actually reach for

CommandWhat it does
HtmlCommandReplaces the inner HTML of a selector, leaving the element itself in place
ReplaceCommandReplaces the selector element itself, including its own tag and attributes
AppendCommandAdds new content inside a selector, after whatever's already there
RemoveCommandDeletes matching elements from the page entirely
InvokeCommandCalls an arbitrary jQuery method on matching elements (e.g. addClass, focus)
CssCommandSets one or more CSS properties directly on matching elements
MessageCommandDisplays a Drupal status/warning/error message via the messenger system

HtmlCommand vs. ReplaceCommand — the distinction that trips people up

// Replaces everything INSIDE #my-wrapper, the wrapper <div> itself survives.
$response->addCommand(new HtmlCommand('#my-wrapper', $new_markup));

// Replaces #my-wrapper ITSELF — a brand-new element takes its place.
$response->addCommand(new ReplaceCommand('#my-wrapper', $new_markup));

This matters more than it looks like it should: if your own new markup doesn't itself include an element with id="my-wrapper", a ReplaceCommand will silently remove your only hook for targeting that region again on a future AJAX request. HtmlCommand avoids that trap entirely, which is why it's the safer default whenever you're not certain which one you need.

A real command in action: opening a modal

use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\OpenModalDialogCommand;

public function openModal(array &$form, FormStateInterface $form_state) {
  $content = ['#markup' => $this->t('This content is dynamically loaded.')];
  $response = new AjaxResponse();
  $response->addCommand(new OpenModalDialogCommand($this->t('Modal Title'), $content, ['width' => '400']));
  return $response;
}

This is real code from the Examples module's ModalForm. Notice it doesn't target a wrapper element at all — OpenModalDialogCommand builds and displays an entirely new dialog on top of the page, which is exactly the kind of thing the render-array shortcut from two lessons ago simply cannot express.

You genuinely won't need most of these often. In real Drupal development, the vast majority of AJAX callbacks reach for HtmlCommand or the plain render-array shortcut and stop there. Knowing the rest of the family exists — and that you can mix several commands in one response — matters far more than memorizing every one's exact constructor signature.

Quick check: you swap out a wrapper's contents with ReplaceCommand, and your replacement markup forgot to include the original wrapper's id attribute. What breaks? (Answer: any future AJAX command still targeting that id selector will silently find nothing and do nothing — the element it used to match no longer exists on the page.)

Key takeaways

  • AJAX commands are a fixed, agreed-upon vocabulary — the server sends a named command object, and Drupal's own JavaScript knows how to carry it out.
  • HtmlCommand replaces a selector's inner content and keeps the wrapper element alive; ReplaceCommand replaces the wrapper element itself.
  • Losing a wrapper's id via a careless ReplaceCommand silently breaks any future command targeting that same selector.
  • Commands like OpenModalDialogCommand can create entirely new UI, not just update an existing wrapper — something the simple render-array shortcut can't do.
  • In practice, most real-world callbacks only ever need HtmlCommand, ReplaceCommand, or the plain render-array return.

Coming up next

You've now seen commands that update, replace, and open new content. Next we focus specifically on updating existing form elements in place via AJAX — the pattern behind dependent selects, live validation feedback, and conditionally-appearing fields.