Implementing AJAX in controllers or formsfor Drupal 11 , 10 , 9 , and 8

Last updated :  

Think about a "like" button that updates its count instantly, with no page flash — that's AJAX doing its job invisibly. Drupal's AJAX framework lets you build exactly that kind of interaction without writing a line of JavaScript yourself: you describe the behavior in PHP, attach a small property, and Drupal generates the actual JavaScript that intercepts the interaction and talks to your server.

What you'll learn in this lesson

  • Two different places AJAX callbacks come from: the #ajax form property, and a standalone AJAX-enabled link
  • The use-ajax class for turning any link into an AJAX request
  • The /nojs/ URL trick for graceful degradation without JavaScript
  • What a callback actually has to return

AJAX outside of forms: the use-ajax class

Most AJAX examples focus on form elements, but Drupal's AJAX framework works on plain links too — this is the real pattern from the Examples module's ajax_example:

$build['ajax_link']['link'] = [
  '#type' => 'link',
  '#title' => $this->t('Click me'),
  '#attached' => ['library' => ['core/drupal.ajax']],
  '#attributes' => ['class' => ['use-ajax']],
  '#url' => Url::fromRoute('ajax_example.ajax_link_callback', ['nojs' => 'nojs']),
];
$build['ajax_link']['destination'] = [
  '#type' => 'container',
  '#attributes' => ['id' => ['ajax-example-destination-div']],
];

Three pieces make this work together: the core/drupal.ajax library must be attached explicitly (forms get this for free; a standalone link doesn't), the use-ajax class is what Drupal's JavaScript watches for and intercepts clicks on, and the destination container's id is where the response will be inserted.

The /nojs/ trick — progressive enhancement built in

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;
  }
  $response = new Response($this->t("This is some content delivered via a page load."));
  return $response;
}

The route path includes a {nojs} placeholder set to the literal string nojs by default. Drupal's client-side AJAX JavaScript rewrites that segment to ajax automatically whenever JavaScript is actually running — so the same callback can tell, purely from the URL it received, whether it's being called via AJAX or via an ordinary full-page link click, and return an appropriate response either way. This is genuine progressive enhancement: the link still works with JavaScript disabled, it just falls back to a full page load instead of an in-place update.

Forms already have this wired up for you: a form element's #ajax property doesn't need the use-ajax class or an explicitly attached library — Drupal's Form API handles all of that automatically the moment it sees an #ajax key. The manual setup above is specifically for AJAX outside a form's own render pipeline — links, or any renderable element you build by hand.

Quick check: if a visitor has JavaScript disabled and clicks the "Click me" link above, what do they get? If you said a full page load showing "This is some content delivered via a page load" — you've understood the /nojs/ mechanism correctly: without JS, the URL segment stays literally nojs, the callback's if branch is never taken, and it falls through to the plain Response.

Key takeaways

  • A form element's #ajax property gets full AJAX wiring automatically; a standalone link needs the use-ajax class and an explicitly attached core/drupal.ajax library.
  • The destination for an AJAX update is any element with a stable id, referenced by whatever command you return.
  • The {nojs} route placeholder, rewritten to ajax client-side when JavaScript is active, lets one callback serve both an AJAX response and a full-page fallback.
  • A callback for a non-form AJAX request returns either an AjaxResponse (with commands) or a plain Response — never a raw render array the way a page controller would.

Coming up next

You saw AppendCommand used above without much explanation — next, a proper look at the full family of AJAX commands, what each one actually does to the page, and when to reach for which.