Basic Concepts

Last updated on
24 February 2025

Overview of Drupal's Ajax API.

This page is based on the "Ajax API" documentation

Why Ajax?

Ajax is the process of dynamically updating parts of a page's HTML based on data from the server without requiring full page refresh. You can find events triggering Ajax requests all over Drupal, for example when selecting Views filters, Views pagers or forms widgets like autocomplete fields.

Ajax Forms example

When a specified event takes place, like entering values in a textbox or clicking a button, a PHP callback is triggered. The callback function performs server-side logic and either

  • returns updated markup to display new information on a page or form without a full page refresh, or 
  • executes JavaScript commands which can display dialog boxes, alter CSS on the page or execute custom JavaScript code.

Any valid DOM event can be used to trigger Ajax requests, simply omit the 'on' portion of the event. For example 'onclick' becomes 'click', 'onchange' is 'change'. Find example code on the Ajax Forms documentation page. Examples events:

  • Clicking a button
  • Pressing a key
  • Entering values in a textbox
  • Selecting a checkbox
  • Moving the mouse

Methods for triggering Ajax requests

Trigger Ajax request from a form

Add an '#ajax' parameter with appropriate values to your form element.

See the AJAX Forms Documentation Page for details on triggering ajax and handling callbacks from a drupal form.
See also

Add class 'use-ajax' to a link. The link will be loaded using an Ajax call. When using this method, the href of the link can contain '/nojs/' as part of the path. When Ajax JavaScript processes the page, it will convert this to '/ajax/'. The server is then able to easily tell if this request was made through an actual Ajax request or in a degraded state, and respond appropriately.

Assume there is custom controller route that returns a render array of markup, defined with the help of a MYMODULE.routing.yml file:

MYMODULE.test:
  path: '/MYMODULE/test'
  defaults:
    _controller: 'Drupal\MYMODULE\Controller\ExampleController:testContent'
  requirements:
    _permission: 'access content'

And a src/Controller/ExampleController.php file:

<?php

namespace Drupal\MYMODULE\Controller;

use Drupal\Core\Controller\ControllerBase;

class ExampleController extends ControllerBase {

  public function testContent() {
    $content = $this->t(__METHOD__);
    $markup = [
      '#type' => 'markup',
      '#markup' => $content,
    ];
  }

}

Here is a link that is part of a form and is able to trigger AJAX opening a modal dialog by use of 'use-ajax' class:

use Drupal\Component\Serialization\Json;
use Drupal\Core\Url;

$form['test_ajax_link'] = [
  '#type' => 'link',
  '#url' => Url::fromRoute('MYMODULE.test'),
  '#title' => 'Click Me',
  '#attributes' => [
    'id' => 'my-dialog',
    'class' => ['use-ajax'],
    'data-dialog-type' => 'modal',
    'data-dialog-options' => Json::encode([
      'width' => 700,
      'minHeight' => 500,
      'title' => $this->t('This is a title'),
    ]),
  ],
];

Trigger Ajax request with a submit button

Add class 'use-ajax-submit' to a submit button in a form. The form will then be submitted via Ajax to the path specified in the #action. Like the ajax-submit class on links, this path will have '/nojs/' replaced with '/ajax/' so that the submit handler can tell if the form was submitted in a degraded state or not.

// This should match with the HTTP verb of the route specified in $form['#action'].
$form['#method'] = 'GET';
// This route should return a Symfony\Component\HttpFoundation\JsonResponse object
// so that the form could handle the result after submit.
$form['#action'] = \Drupal\Core\Url::fromRoute('MYMODULE.test')->toString();
// A HTML element that is part of the form where AJAX submit result could be inserted.
$form['result'] = [
  '#markup' => '<div id="result"></div>',
];
// Makes AJAX request to $form['#action'] route aka `MYMODULE.test` route.
$form['submit'] = [
  '#type' => 'submit',
  '#value' => $this->t('Submit'),
  '#attributes' => [
    'class' => ['use-ajax-submit'],
  ],
];

Here is how the MYMODULE.test controller route may look like:

<?php

namespace Drupal\MYMODULE\Controller;

use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\JsonResponse;

class TestController extends ControllerBase {

  // Perhaps tweak the route a bit so it has some parameters
  // for the selector, the command, making this a bit more dynamic.
  // For simplicity, hard-code them for now.
  public function testContent() {
    $content = $this->t(__METHOD__);
    return new JsonResponse([
      'command' => 'insert',
      'selector' => '#result',
      'data' => $content,
    ]);
  }

}

Finally, a custom JS code to make use of the JsonResponse object. Don't forget to declare a library in MYMODULE.libraries.yml file and attach it to the form.

$form['#attached']['library'][] = 'MYMODULE/my_library';
(function ($) {
  $(document).on('ajaxComplete', function (event, request, settings) {
    if (!('responseJSON' in request)) {
      return;
    }
    console.log(request.responseJSON);
    const {command, selector, data} = request.responseJSON;
    if (command === 'insert') {
      $(selector).html(data);
    }
  });
})(jQuery);

Trigger Ajax request with custom code (Client JavaScript)

To trigger an Ajax request in custom JavaScript code on the client, you can find the related code in /misc/ajax.js Javascript file. The key API function is Drupal.ajax() - see its JSDoc below for more details.

To make a simple AJAX call to a URL (or probably your Drupal Ajax Command URL)

const ajax = Drupal.ajax({
  url: '/your-ajax-url?_wrapper_format=drupal_ajax',
});
ajax.execute();

More details and examples in the JSDoc:

/**
* Provides Ajax page updating via jQuery $.ajax.
*
* This function is designed to improve developer experience by wrapping the
* initialization of {@link Drupal.Ajax} objects and storing all created
* objects in the {@link Drupal.ajax.instances} array.
*
* @example
* Drupal.behaviors.myCustomAJAXStuff = {
* attach: function (context, settings) {
*
*   var ajaxSettings = {
*     url: 'my/url/path',
*     // If the old version of Drupal.ajax() needs to be used those
*     // properties can be added
*     base: 'myBase',
*     element: $(context).find('.someElement')
*   };
*
*   var myAjaxObject = Drupal.ajax(ajaxSettings);
*
*   // Declare a new Ajax command specifically for this Ajax object.
*   myAjaxObject.commands.insert = function (ajax, response, status) {
*     $('#my-wrapper').append(response.data);
*     alert('New content was appended to #my-wrapper');
*   };
*
*   // This command will remove this Ajax object from the page.
*   myAjaxObject.commands.destroyObject = function (ajax, response, status) {
*     Drupal.ajax.instances[this.instanceIndex] = null;
*   };
*
*   // Programmatically trigger the Ajax request.
*   myAjaxObject.execute();
* }
* };
*
* @param {object} settings
* The settings object passed to {@link Drupal.Ajax} constructor.
* @param {string} [settings.base]
* Base is passed to {@link Drupal.Ajax} constructor as the 'base'
* parameter.
* @param {HTMLElement} [settings.element]
* Element parameter of {@link Drupal.Ajax} constructor, element on which
* event listeners will be bound.
*
* @return {Drupal.Ajax}
* The created Ajax object.
*
* @see Drupal.AjaxCommands
*/
Drupal.ajax = function (settings) {...}

Autocomplete on text fields

Add property '#autocomplete_route_name' to a text field in a form. The route controller for this route must return an array of options for autocomplete, as a \Symfony\Component\HttpFoundation\JsonResponse object. See the Routing topic for more information about routing.

It is worth checking out the Drupal\Core\Entity\Element\EntityAutocomplete element.

Do NOT attempt to set '#autocomplete_route_name' and '#autocomplete_route_parameters' by yourself because that is automatically handled by Drupal\Core\Entity\Element\EntityAutocomplete::processEntityAutocomplete() method which also updates the 'entity_autocomplete' keyvalue store for performance. If you set the '#autocomplete_*' manually, the keyvalue store never gets updated properly, which causes issue.

$form['test_autocomplete'] = [
  '#type' => 'entity_autocomplete',
  '#target_type' => 'node',
  '#tags' => TRUE,
  '#selection_handler' => 'default',
  '#selection_settings' => [
    'target_bundles' => ['article',],
  ],
  '#autocreate' => [
    'bundle' => 'article',
    'uid' => 1,
  ],
];

Also check out the linkit and dynamic_entity_reference contrib modules for more autocomplete examples.

Drupal Ajax Conventions

Replacing markup

It is best practice to use an element's id attribute to target it for replacement to avoid any chance of the wrong element or multiple elements being replaced unless that is specifically required. In order to maintain the replaceability of that element, the replacement render array or HTML markup should contain a wrapper element with the same id as the element that was being replaced.

When working with render arrays, if such a wrapper is not automatically included it is common practice to use the '#prefix' and '#suffix' render variables at the top level to provide one.

Help improve this page

Page status: No known problems

You can: