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
#ajaxform property, and a standalone AJAX-enabled link - The
use-ajaxclass 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.
#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 literallynojs, the callback'sifbranch is never taken, and it falls through to the plainResponse.
Key takeaways
- A form element's
#ajaxproperty gets full AJAX wiring automatically; a standalone link needs theuse-ajaxclass and an explicitly attachedcore/drupal.ajaxlibrary. - The destination for an AJAX update is any element with a stable
id, referenced by whatever command you return. - The
{nojs}route placeholder, rewritten toajaxclient-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 plainResponse— 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.