diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 5402daeb62f4ce98d80afaa617b818aee716e4bc..ef54c93c5f4b793679690519058984473ec52849 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -627,8 +627,14 @@ function theme($hook, $variables = array()) {
   if (isset($info['base hook'])) {
     $suggestions[] = $hook;
   }
-  // Allow suggestions to be altered via hook_theme_suggestions_HOOK_alter().
-  Drupal::moduleHandler()->alter('theme_suggestions_' . $base_theme_hook, $suggestions, $variables);
+
+  // Invoke hook_theme_suggestions_alter() and
+  // hook_theme_suggestions_HOOK_alter().
+  $hooks = array(
+    'theme_suggestions',
+    'theme_suggestions_' . $base_theme_hook,
+  );
+  \Drupal::moduleHandler()->alter($hooks, $suggestions, $variables, $base_theme_hook);
 
   // Check if each suggestion exists in the theme registry, and if so,
   // use it instead of the hook that theme() was called with. For example, a
diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeSuggestionsAlterTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeSuggestionsAlterTest.php
index 00cd0081aeac282bec75d9578e92b7c9b06f19f8..8f6e9b6f1f60a0e2f3d2018b1a313d7ec5a721d1 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeSuggestionsAlterTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeSuggestionsAlterTest.php
@@ -51,25 +51,46 @@ function testTemplateSuggestions() {
     $this->assertText('Template overridden based on suggestion provided by the module declaring the theme hook.');
   }
 
+  /**
+   * Tests hook_theme_suggestions_alter().
+   */
+  function testGeneralSuggestionsAlter() {
+    $this->drupalGet('theme-test/general-suggestion-alter');
+    $this->assertText('Original template for testing hook_theme_suggestions_alter().');
+
+    // Enable test_theme and test that themes can alter template suggestions.
+    config('system.theme')
+      ->set('default', 'test_theme')
+      ->save();
+    $this->drupalGet('theme-test/general-suggestion-alter');
+    $this->assertText('Template overridden based on new theme suggestion provided by the test_theme theme via hook_theme_suggestions_alter().');
+
+    // Enable the theme_suggestions_test module to test modules implementing
+    // suggestions alter hooks.
+    \Drupal::moduleHandler()->install(array('theme_suggestions_test'));
+    $this->drupalGet('theme-test/general-suggestion-alter');
+    $this->assertText('Template overridden based on new theme suggestion provided by a module via hook_theme_suggestions_alter().');
+  }
+
   /**
    * Tests that theme suggestion alter hooks work for templates.
    */
   function testTemplateSuggestionsAlter() {
     $this->drupalGet('theme-test/suggestion-alter');
-    $this->assertText('Original template.');
+    $this->assertText('Original template for testing hook_theme_suggestions_HOOK_alter().');
 
     // Enable test_theme and test that themes can alter template suggestions.
     config('system.theme')
       ->set('default', 'test_theme')
       ->save();
     $this->drupalGet('theme-test/suggestion-alter');
-    $this->assertText('Template overridden based on new theme suggestion provided by the test_theme theme.');
+    $this->assertText('Template overridden based on new theme suggestion provided by the test_theme theme via hook_theme_suggestions_HOOK_alter().');
 
     // Enable the theme_suggestions_test module to test modules implementing
     // suggestions alter hooks.
     \Drupal::moduleHandler()->install(array('theme_suggestions_test'));
     $this->drupalGet('theme-test/suggestion-alter');
-    $this->assertText('Template overridden based on new theme suggestion provided by a module.');
+    $this->assertText('Template overridden based on new theme suggestion provided by a module via hook_theme_suggestions_HOOK_alter().');
   }
 
   /**
@@ -117,4 +138,34 @@ function testThemeFunctionSuggestionsAlter() {
     $this->assertText('Theme function overridden based on new theme suggestion provided by a module.');
   }
 
+  /**
+   * Tests execution order of theme suggestion alter hooks.
+   *
+   * hook_theme_suggestions_alter() should fire before
+   * hook_theme_suggestions_HOOK_alter() within an extension (module or theme).
+   */
+  function testExecutionOrder() {
+    // Enable our test theme and module.
+    config('system.theme')
+      ->set('default', 'test_theme')
+      ->save();
+    \Drupal::moduleHandler()->install(array('theme_suggestions_test'));
+
+    // Send two requests so that we get all the messages we've set via
+    // drupal_set_message().
+    $this->drupalGet('theme-test/suggestion-alter');
+    // Ensure that the order is first by extension, then for a given extension,
+    // the hook-specific one after the generic one.
+    $expected = array(
+      'theme_suggestions_test_theme_suggestions_alter() executed.',
+      'theme_suggestions_test_theme_suggestions_theme_test_suggestions_alter() executed.',
+      'theme_test_theme_suggestions_alter() executed.',
+      'theme_test_theme_suggestions_theme_test_suggestions_alter() executed.',
+      'test_theme_theme_suggestions_alter() executed.',
+      'test_theme_theme_suggestions_theme_test_suggestions_alter() executed.',
+    );
+    $content = preg_replace('/\s+/', ' ', filter_xss($this->content, array()));
+    $this->assert(strpos($content, implode(' ', $expected)) !== FALSE, 'Suggestion alter hooks executed in the expected order.');
+  }
+
 }
diff --git a/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.module b/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.module
index 9ecaeebd63bc358ff71d3a888ae90e0bb51d220f..1b19fee5edcb0460cd83d4c2c6b1c06de02cf8a5 100644
--- a/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.module
+++ b/core/modules/system/tests/modules/theme_suggestions_test/theme_suggestions_test.module
@@ -5,10 +5,21 @@
  * Support module for testing theme suggestions.
  */
 
+/**
+ * Implements hook_theme_suggestions_alter().
+ */
+function theme_suggestions_test_theme_suggestions_alter(array &$suggestions, array $variables, $hook) {
+  drupal_set_message(__FUNCTION__ . '() executed.');
+  if ($hook == 'theme_test_general_suggestions') {
+    $suggestions[] = $hook . '__module_override';
+  }
+}
+
 /**
  * Implements hook_theme_suggestions_HOOK_alter().
  */
 function theme_suggestions_test_theme_suggestions_theme_test_suggestions_alter(array &$suggestions, array $variables) {
+  drupal_set_message(__FUNCTION__ . '() executed.');
   $suggestions[] = 'theme_test_suggestions__' . 'module_override';
 }
 
diff --git a/core/modules/system/tests/modules/theme_test/lib/Drupal/theme_test/ThemeTestController.php b/core/modules/system/tests/modules/theme_test/lib/Drupal/theme_test/ThemeTestController.php
index 8da6c62206c0c4d59aa210b16cfaeb23a94ecc4b..29bfa4eff5bd7d141f887c8eb33feafdf6badb10 100644
--- a/core/modules/system/tests/modules/theme_test/lib/Drupal/theme_test/ThemeTestController.php
+++ b/core/modules/system/tests/modules/theme_test/lib/Drupal/theme_test/ThemeTestController.php
@@ -106,6 +106,13 @@ function suggestionAlter() {
     return array('#theme' => 'theme_test_suggestions');
   }
 
+  /**
+   * Menu callback for testing hook_theme_suggestions_alter().
+   */
+  function generalSuggestionAlter() {
+    return array('#theme' => 'theme_test_general_suggestions');
+  }
+
   /**
    * Menu callback for testing suggestion alter hooks with specific suggestions.
    */
diff --git a/core/modules/system/tests/modules/theme_test/templates/theme-test-general-suggestions.html.twig b/core/modules/system/tests/modules/theme_test/templates/theme-test-general-suggestions.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..086eb47911ca3d90ef800a9b042c759f1873a220
--- /dev/null
+++ b/core/modules/system/tests/modules/theme_test/templates/theme-test-general-suggestions.html.twig
@@ -0,0 +1,2 @@
+{# Output for Theme API test #}
+Original template for testing hook_theme_suggestions_alter().
diff --git a/core/modules/system/tests/modules/theme_test/templates/theme-test-suggestions.html.twig b/core/modules/system/tests/modules/theme_test/templates/theme-test-suggestions.html.twig
index dfc848c94d9c6d9e22829bb880acca8d97efb4ea..42bb3b94e93c23de630de781cc12ee528a0ef60d 100644
--- a/core/modules/system/tests/modules/theme_test/templates/theme-test-suggestions.html.twig
+++ b/core/modules/system/tests/modules/theme_test/templates/theme-test-suggestions.html.twig
@@ -1,2 +1,2 @@
 {# Output for Theme API test #}
-Original template.
+Original template for testing hook_theme_suggestions_HOOK_alter().
diff --git a/core/modules/system/tests/modules/theme_test/theme_test.module b/core/modules/system/tests/modules/theme_test/theme_test.module
index b8a528d4a87943d41243835d1c98fc06e6754928..bdb1404695a6b094b4c2f90e7e2650fe9a4fe7d7 100644
--- a/core/modules/system/tests/modules/theme_test/theme_test.module
+++ b/core/modules/system/tests/modules/theme_test/theme_test.module
@@ -26,6 +26,10 @@ function theme_test_theme($existing, $type, $theme, $path) {
     'template' => 'theme-test-suggestions',
     'variables' => array(),
   );
+  $items['theme_test_general_suggestions'] = array(
+    'template' => 'theme-test-general-suggestions',
+    'variables' => array(),
+  );
   $items['theme_test_function_suggestions'] = array(
     'variables' => array(),
   );
@@ -152,3 +156,17 @@ function theme_theme_test_function_suggestions($variables) {
 function theme_test_theme_suggestions_theme_test_suggestion_provided(array $variables) {
   return array('theme_test_suggestion_provided__' . 'foo');
 }
+
+/**
+ * Implements hook_theme_suggestions_alter().
+ */
+function theme_test_theme_suggestions_alter(array &$suggestions, array $variables, $hook) {
+  drupal_set_message(__FUNCTION__ . '() executed.');
+}
+
+/**
+ * Implements hook_theme_suggestions_HOOK_alter().
+ */
+function theme_test_theme_suggestions_theme_test_suggestions_alter(array &$suggestions, array $variables) {
+  drupal_set_message(__FUNCTION__ . '() executed.');
+}
diff --git a/core/modules/system/tests/modules/theme_test/theme_test.routing.yml b/core/modules/system/tests/modules/theme_test/theme_test.routing.yml
index 9f08d5b0c56ae2ded56233dcf65b5eb8f13d57c5..6b78b13516fc4ab3d2b2f5f5cab08c286c01ab2a 100644
--- a/core/modules/system/tests/modules/theme_test/theme_test.routing.yml
+++ b/core/modules/system/tests/modules/theme_test/theme_test.routing.yml
@@ -65,6 +65,13 @@ suggestion_alter:
   requirements:
     _permission: 'access content'
 
+theme_test.general_suggestion_alter:
+  path: '/theme-test/general-suggestion-alter'
+  defaults:
+    _content: '\Drupal\theme_test\ThemeTestController::generalSuggestionAlter'
+  requirements:
+    _permission: 'access content'
+
 suggestion_provided:
   path: '/theme-test/suggestion-provided'
   defaults:
diff --git a/core/modules/system/tests/themes/test_theme/templates/theme-test-general-suggestions--module-override.html.twig b/core/modules/system/tests/themes/test_theme/templates/theme-test-general-suggestions--module-override.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..5969332bcb044a5cb655b30b4177be610ac27ed4
--- /dev/null
+++ b/core/modules/system/tests/themes/test_theme/templates/theme-test-general-suggestions--module-override.html.twig
@@ -0,0 +1,2 @@
+{# Output for Theme API test #}
+Template overridden based on new theme suggestion provided by a module via hook_theme_suggestions_alter().
diff --git a/core/modules/system/tests/themes/test_theme/templates/theme-test-general-suggestions--theme-override.html.twig b/core/modules/system/tests/themes/test_theme/templates/theme-test-general-suggestions--theme-override.html.twig
new file mode 100644
index 0000000000000000000000000000000000000000..29322daeabce3366d5d82042a347e185975fd75b
--- /dev/null
+++ b/core/modules/system/tests/themes/test_theme/templates/theme-test-general-suggestions--theme-override.html.twig
@@ -0,0 +1,2 @@
+{# Output for Theme API test #}
+Template overridden based on new theme suggestion provided by the test_theme theme via hook_theme_suggestions_alter().
diff --git a/core/modules/system/tests/themes/test_theme/templates/theme-test-suggestions--module-override.html.twig b/core/modules/system/tests/themes/test_theme/templates/theme-test-suggestions--module-override.html.twig
index 26ce57bdae283f71339761d0ff11589df1168bc5..2a005cbc6c66fd3d6dbca2e9092e8ac2ee596329 100644
--- a/core/modules/system/tests/themes/test_theme/templates/theme-test-suggestions--module-override.html.twig
+++ b/core/modules/system/tests/themes/test_theme/templates/theme-test-suggestions--module-override.html.twig
@@ -1,2 +1,2 @@
 {# Output for Theme API test #}
-Template overridden based on new theme suggestion provided by a module.
+Template overridden based on new theme suggestion provided by a module via hook_theme_suggestions_HOOK_alter().
diff --git a/core/modules/system/tests/themes/test_theme/templates/theme-test-suggestions--theme-override.html.twig b/core/modules/system/tests/themes/test_theme/templates/theme-test-suggestions--theme-override.html.twig
index dee829f9750eb89c1093c2fc89127c4a7c7f3348..45162387fb037ba834eb261ddd1c9266d94a00f3 100644
--- a/core/modules/system/tests/themes/test_theme/templates/theme-test-suggestions--theme-override.html.twig
+++ b/core/modules/system/tests/themes/test_theme/templates/theme-test-suggestions--theme-override.html.twig
@@ -1,2 +1,2 @@
 {# Output for Theme API test #}
-Template overridden based on new theme suggestion provided by the test_theme theme.
+Template overridden based on new theme suggestion provided by the test_theme theme via hook_theme_suggestions_HOOK_alter().
diff --git a/core/modules/system/tests/themes/test_theme/test_theme.theme b/core/modules/system/tests/themes/test_theme/test_theme.theme
index 9b10b2b03d2346f37638a633ec6d68219a474222..13cb2b9bc9080f28ec8cf2dbf35159e8797a23df 100644
--- a/core/modules/system/tests/themes/test_theme/test_theme.theme
+++ b/core/modules/system/tests/themes/test_theme/test_theme.theme
@@ -30,10 +30,25 @@ function test_theme_theme_test_alter_alter(&$data) {
   $data = 'test_theme_theme_test_alter_alter was invoked';
 }
 
+/**
+ * Implements hook_theme_suggestions_alter().
+ */
+function test_theme_theme_suggestions_alter(array &$suggestions, array $variables, $hook) {
+  drupal_set_message(__FUNCTION__ . '() executed.');
+  // Theme alter hooks run after module alter hooks, so add this theme
+  // suggestion to the beginning of the array so that the suggestion added by
+  // the theme_suggestions_test module can be picked up when that module is
+  // enabled.
+  if ($hook == 'theme_test_general_suggestions') {
+    array_unshift($suggestions, 'theme_test_general_suggestions__' . 'theme_override');
+  }
+}
+
 /**
  * Implements hook_theme_suggestions_HOOK_alter().
  */
 function test_theme_theme_suggestions_theme_test_suggestions_alter(array &$suggestions, array $variables) {
+  drupal_set_message(__FUNCTION__ . '() executed.');
   // Theme alter hooks run after module alter hooks, so add this theme
   // suggestion to the beginning of the array so that the suggestion added by
   // the theme_suggestions_test module can be picked up when that module is
diff --git a/core/modules/system/theme.api.php b/core/modules/system/theme.api.php
index 34758dafda123eba2e199b2a19c6ba9c12f0fc04..db11a21c40482f12f91d244c1810fb122402d46d 100644
--- a/core/modules/system/theme.api.php
+++ b/core/modules/system/theme.api.php
@@ -189,6 +189,55 @@ function hook_theme_suggestions_HOOK(array $variables) {
   return $suggestions;
 }
 
+/**
+ * Alters named suggestions for all theme hooks.
+ *
+ * This hook is invoked for all theme hooks, if you are targeting a specific
+ * theme hook it's best to use hook_theme_suggestions_HOOK_alter().
+ *
+ * The call order is as follows: all existing suggestion alter functions are
+ * called for module A, then all for module B, etc., followed by all for any
+ * base theme(s), and finally for the active theme. The order is
+ * determined by system weight, then by extension (module or theme) name.
+ *
+ * Within each module or theme, suggestion alter hooks are called in the
+ * following order: first, hook_theme_suggestions_alter(); second,
+ * hook_theme_suggestions_HOOK_alter(). So, for each module or theme, the more
+ * general hooks are called first followed by the more specific.
+ *
+ * In the following example, we provide an alternative template suggestion to
+ * node and taxonomy term templates based on the user being logged in.
+ * @code
+ * function MYMODULE_theme_suggestions_alter(array &$suggestions, array $variables, $hook) {
+ *   if (\Drupal::currentUser()->isAuthenticated() && in_array($hook, array('node', 'taxonomy_term'))) {
+ *     $suggestions[] = $hook . '__' . 'logged_in';
+ *   }
+ * }
+ *
+ * @endcode
+ *
+ * @param array $suggestions
+ *   An array of alternate, more specific names for template files or theme
+ *   functions.
+ * @param array $variables
+ *   An array of variables passed to the theme hook. Note that this hook is
+ *   invoked before any variable preprocessing.
+ * @param string $hook
+ *   The base hook name. For example, if '#theme' => 'node__article' is called,
+ *   then $hook will be 'node', not 'node__article'. The specific hook called
+ *   (in this case 'node__article') is available in
+ *   $variables['theme_hook_original'].
+ *
+ * @return array
+ *   An array of theme suggestions.
+ *
+ * @see hook_theme_suggestions_HOOK_alter()
+ */
+function hook_theme_suggestions_alter(array &$suggestions, array $variables, $hook) {
+  // Add an interface-language specific suggestion to all theme hooks.
+  $suggestions[] = $hook . '__' . \Drupal::languageManager()->getLanguage()->id;
+}
+
 /**
  * Alters named suggestions for a specific theme hook.
  *
@@ -210,6 +259,7 @@ function hook_theme_suggestions_HOOK(array $variables) {
  *   An array of variables passed to the theme hook. Note that this hook is
  *   invoked before any preprocessing.
  *
+ * @see hook_theme_suggestions_alter()
  * @see hook_theme_suggestions_HOOK()
  */
 function hook_theme_suggestions_HOOK_alter(array &$suggestions, array $variables) {