I would like to send a message to the user on an event in the backend without a refresh. I have a 2fa login form that will send a token text. That form has a "did not receive sms" button/link using ajax that causes the text to be sent again. I would like to inform the customer of the incoming message.

You can send a notification to the user using the MessengerInterface. But this requires a refresh to show the message.

\Drupal::messenger()->addStatus('Some conditional message to the user');

I would imagine that there is a way to send a message to the user using ajax/XHR? Which would not require a refresh.

My form has a wrapping div which I can target, the button/link has an '#ajax' element with, as far as I know, all the prerequisite elements:

'#ajax' => [
  'callback' => '::ajaxCallback',
  'event' => 'click',
  'wrapper' => 'twofa_wrapper',

I found this comment on the Drupal forum but using this code, with obvious replacements, does nothing apart from sending a XHR response, visible in the network tab. The json response contains a number of things one being the html for the form which contains the message that I would like to show, but it doesn't show this content on the actual page.

public function ajaxCallback(array &$form, FormStateInterface $form_state) {
  $response = new AjaxResponse();
  // This by some Drupal magic gets the messages
  $messages = ['#type' => 'status_messages']; 
  $messages = \Drupal::service('renderer')->render($message);
  $response->addCommand(new HtmlCommand('#twofa_wrapper', $messages));
  $response->addCommand(new ReplaceCommand(null, $form));
  return $response;

What am I missing?


The wrapping element was (is) not rendering for some reason, if I include it in the template the callback shows the messages in that element at the top of the form.

The problem is that I need to renew the entire form because the contents might change on the ajax call. Replacing the contents works when I actually enclose the form in the wrapper element, but then the messages are no longer included.

I can see that the messages are part of the response but separate from the form contents. Second element messages third element the new form. The strange thing is the message are also not shown on a refresh.

enter image description here

1 Answer 1


This works for me:

$element = NULL;
if (!empty($triggering_element['#ajax']['element'])) {
  $element = \Drupal\Component\Utility\NestedArray::getValue($form, $triggering_element['#ajax']['element']);
// Element not specified or not found. Show messages on top of the form.
if (!$element) {
  $element = $form;
$response = new \Drupal\Core\Ajax\AjaxResponse();
$response->addCommand(new \Drupal\Core\Ajax\ReplaceCommand('[data-drupal-selector="' . $form['#attributes']['data-drupal-selector'] . '"]', $form));
$response->addCommand(new \Drupal\Core\Ajax\PrependCommand('[data-drupal-selector="' . $element['#attributes']['data-drupal-selector'] . '"]', ['#type' => 'status_messages']));
return $response;

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.