diff --git a/core/modules/views/js/exposed-form-ajax.js b/core/modules/views/js/exposed-form-ajax.js
new file mode 100644
index 0000000..86857d6
--- /dev/null
+++ b/core/modules/views/js/exposed-form-ajax.js
@@ -0,0 +1,24 @@
+/**
+ * @file
+ * Handles Views' exposed form AJAX data submission.
+ */
+(function ($) {
+
+  /*
+   * Gets Form build info from settings and adds it to ajax data.
+   *
+   * @see views_exposed_form_ajax_enable().
+   */
+  Drupal.behaviors.ViewsExposedFormAjax = {
+    attach: function(context, settings) {
+      for (var ajaxObject in Drupal.ajax.instances) {
+        for(var name in drupalSettings.ViewsExposedFormInfo) {
+          if (Drupal.ajax.instances[ajaxObject].options && Drupal.ajax.instances[ajaxObject].options.data._triggering_element_name == name) {
+            jQuery.extend(Drupal.ajax.instances[ajaxObject].options.data, drupalSettings.ViewsExposedFormInfo[name]);
+          }
+        }
+      }
+    }
+  };
+
+})(jQuery);
diff --git a/core/modules/views/tests/modules/views_test_exposed_filter/views_test_exposed_filter.info.yml b/core/modules/views/tests/modules/views_test_exposed_filter/views_test_exposed_filter.info.yml
new file mode 100644
index 0000000..e0472bc
--- /dev/null
+++ b/core/modules/views/tests/modules/views_test_exposed_filter/views_test_exposed_filter.info.yml
@@ -0,0 +1,8 @@
+name: 'Views Test Exposed Filter'
+type: module
+description: 'Test module for Views.'
+package: Testing
+version: VERSION
+core: 8.x
+dependencies:
+  - views
diff --git a/core/modules/views/tests/modules/views_test_exposed_filter/views_test_exposed_filter.module b/core/modules/views/tests/modules/views_test_exposed_filter/views_test_exposed_filter.module
new file mode 100644
index 0000000..35ddfc3
--- /dev/null
+++ b/core/modules/views/tests/modules/views_test_exposed_filter/views_test_exposed_filter.module
@@ -0,0 +1,20 @@
+<?php
+
+use \Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function views_test_exposed_filter_form_views_exposed_form_alter(&$form, FormStateInterface $form_state) {
+  if (isset($form['title'])) {
+    $form['title']['#ajax']['callback'] = 'views_test_exposed_filter_ajax_callback';
+    $form['title']['#ajax']['wrapper'] = 'views-test-exposed-filter-test';
+    $form['title']['#prefix'] = '<span id="views-test-exposed-filter-test">Default prefix</span>';
+  }
+}
+
+function views_test_exposed_filter_ajax_callback(array &$form, FormStateInterface $form_state) {
+  return [
+    '#markup' => 'Callback called.',
+  ];
+}
\ No newline at end of file
diff --git a/core/modules/views/tests/src/FunctionalJavascript/ExposedFilterAJAXTest.php b/core/modules/views/tests/src/FunctionalJavascript/ExposedFilterAJAXTest.php
index 974aa2d..0857cb7 100644
--- a/core/modules/views/tests/src/FunctionalJavascript/ExposedFilterAJAXTest.php
+++ b/core/modules/views/tests/src/FunctionalJavascript/ExposedFilterAJAXTest.php
@@ -5,6 +5,7 @@
 use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
 use Drupal\simpletest\ContentTypeCreationTrait;
 use Drupal\simpletest\NodeCreationTrait;
+use Drupal\views\Tests\ViewTestData;
 
 /**
  * Tests the basic AJAX functionality of Views exposed forms.
@@ -19,7 +20,12 @@ class ExposedFilterAJAXTest extends JavascriptTestBase {
   /**
    * {@inheritdoc}
    */
-  public static $modules = ['node', 'views'];
+  public static $modules = ['node', 'views', 'views_test_config'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $testViews = ['test_content_ajax'];
 
   /**
    * Tests if exposed filtering via AJAX works for the "Content" View.
@@ -80,4 +86,37 @@ public function testExposedFiltering() {
     $this->assertFalse($session->getPage()->hasButton('Reset'));
   }
 
+  /**
+   * Tests if AJAX events can be attached to the exposed filter form.
+   */
+  public function testExposedFilterAjaxCallback() {
+
+    ViewTestData::createTestViews(self::class, ['views_test_config']);
+
+    // Attach an AJAX event to all 'title' fields in the exposed filter form.
+    \Drupal::service('module_installer')->install(['views_test_exposed_filter']);
+    $this->resetAll();
+    $this->rebuildContainer();
+    $this->container->get('module_handler')->reload();
+
+    // Create a user privileged enough to view content.
+    $user = $this->drupalCreateUser([
+      'administer site configuration',
+      'access content',
+      'access content overview',
+    ]);
+    $this->drupalLogin($user);
+
+    $this->drupalGet('test-content-ajax');
+
+    $page = $this->getSession()->getPage();
+    $this->createScreenshot('/tmp/screen1.jpg');
+    $this->assertSession()->pageTextContains('Default prefix');
+    $page->fillField('title', 'value');
+    $this->createScreenshot('/tmp/screen2.jpg');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+$this->createScreenshot('/tmp/screen3.jpg');
+    $this->assertSession()->pageTextContains('Callback called.');
+  }
+
 }
diff --git a/core/modules/views/views.libraries.yml b/core/modules/views/views.libraries.yml
index 640492d..8208a44 100644
--- a/core/modules/views/views.libraries.yml
+++ b/core/modules/views/views.libraries.yml
@@ -16,3 +16,15 @@ views.ajax:
     - core/jquery.once
     - core/jquery.form
     - core/drupal.ajax
+
+views.exposed-form-ajax:
+  version: VERSION
+  js:
+    js/exposed-form-ajax.js: {}
+  dependencies:
+    - core/jquery
+    - core/drupal
+    - core/drupalSettings
+    - core/jquery.once
+    - core/jquery.form
+    - core/drupal.ajax
diff --git a/core/modules/views/views.module b/core/modules/views/views.module
index e1a4d8c..50aeb68 100644
--- a/core/modules/views/views.module
+++ b/core/modules/views/views.module
@@ -10,6 +10,7 @@
 use Drupal\Core\Database\Query\AlterableInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Url;
 use Drupal\views\Plugin\Derivative\ViewsLocalTask;
@@ -639,11 +640,92 @@ function views_pre_render_views_form_views_form($element) {
  *
  * Since the exposed form is a GET form, we don't want it to send a wide
  * variety of information.
+ *
+ * @param array $form
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
  */
 function views_form_views_exposed_form_alter(&$form, FormStateInterface $form_state) {
   $form['form_build_id']['#access'] = FALSE;
   $form['form_token']['#access'] = FALSE;
   $form['form_id']['#access'] = FALSE;
+  // AJAX behaviors need these data, so we add it back in #after_build.
+  $form['#after_build'][] = 'views_exposed_form_ajax_enable';
+}
+
+/**
+ * Add previously removed required form data.
+ *
+ * Checks whether the exposed form will use ajax and passes required
+ * form information removed in views_form_views_exposed_form_alter().
+ *
+ * @param array $form
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ *
+ * @return array
+ */
+function views_exposed_form_ajax_enable(&$form, FormStateInterface $form_state) {
+  // In order for Ajax to work, we need the form build info. Here we
+  // check if #ajax has been added to any form elements, and if so,
+  // pass this info as settings via Javascript, which get attached to
+  // the submitted form on Ajax form submissions.
+  // #ajax property can be set not only for the first level of the form,
+  // so we look for it in the whole form.
+  $ajax_info = [];
+  $ajax_elements = views_exposed_form_ajax_lookup_recursive($form);
+  // Determine if form is being processed.
+
+  $triggering_element = $form_state->getTriggeringElement();
+  if (!empty($triggering_element['#parents'])) {
+    $triggering_element_name = end($triggering_element['#parents']);
+  }
+  // When we have multiple elements with #ajax set on exposed form
+  // we need to pass only triggering element name to Javascript.
+  // Otherwise #ajax will work only for the first element.
+  if (!empty($triggering_element_name) && !empty($ajax_elements)) {
+    // Check if element has #ajax property set.
+    if (in_array($triggering_element_name, $ajax_elements)) {
+      $ajax_elements = [$triggering_element_name => $triggering_element_name];
+    }
+    else {
+      $ajax_elements = [];
+    }
+  }
+  if (!empty($ajax_elements)) {
+    $form_info = [
+      'form_id' => $form['#form_id'],
+      'form_build_id' => $form['#build_id'],
+    ];
+    // Anonymous users don't get a token.
+    if (!empty($form['#token'])) {
+      $form_info['form_token'] = drupal_get_token($form['#token']);
+    }
+    foreach ($ajax_elements as $element_name) {
+       $ajax_info[$element_name] = $form_info;
+    }
+    $form['#attached']['drupalSettings']['ViewsExposedFormInfo'] = $ajax_info;
+    $form['#attached']['library'][] = 'views/views.exposed-form-ajax';
+  }
+  return $form;
+}
+
+/**
+ * Recursively looks for the #ajax property for every form elemet.
+ * @param $elements
+ *   The element array to look for #ajax property.
+ *
+ * @return array
+ *   Array of the elements names where #ajax was found.
+ */
+function views_exposed_form_ajax_lookup_recursive($elements) {
+  $ajax_elements = [];
+  foreach (Element::children($elements) as $key) {
+    if (!empty($elements[$key]['#name']) && !empty($elements[$key]['#ajax'])) {
+      $ajax_elements[$elements[$key]['#name']] = $elements[$key]['#name'];
+    }
+    // Recursive call to look for #ajax in element's childrens.
+    $ajax_elements += views_exposed_form_ajax_lookup_recursive($elements[$key]);
+  }
+  return $ajax_elements;
 }
 
 /**
