diff --git a/core/lib/Drupal/Core/Menu/ContextualLinkManagerInterface.php b/core/lib/Drupal/Core/Menu/ContextualLinkManagerInterface.php
index 12f0275584..6e9c69a00f 100644
--- a/core/lib/Drupal/Core/Menu/ContextualLinkManagerInterface.php
+++ b/core/lib/Drupal/Core/Menu/ContextualLinkManagerInterface.php
@@ -2,10 +2,12 @@
 
 namespace Drupal\Core\Menu;
 
+use Drupal\Component\Plugin\PluginManagerInterface;
+
 /**
  * Provides an object which returns the available contextual links.
  */
-interface ContextualLinkManagerInterface {
+interface ContextualLinkManagerInterface extends PluginManagerInterface {
 
   /**
    * Gets the contextual link plugins by contextual link group.
diff --git a/core/modules/contextual/contextual.module b/core/modules/contextual/contextual.module
index a814c391f1..afcebbcd51 100644
--- a/core/modules/contextual/contextual.module
+++ b/core/modules/contextual/contextual.module
@@ -9,6 +9,7 @@
 use Drupal\Component\Utility\UrlHelper;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Site\Settings;
 
 /**
  * Implements hook_toolbar().
@@ -65,7 +66,13 @@ function contextual_page_attachments(array &$page) {
     return;
   }
 
+  /** @var \Drupal\Core\Menu\ContextualLinkManagerInterface $cl_manager */
+  $cl_manager = \Drupal::service('plugin.manager.menu.contextual_link');
+  $cl_definitions = $cl_manager->getDefinitions();
+  ksort($cl_definitions);
+
   $page['#attached']['library'][] = 'contextual/drupal.contextual-links';
+  $page['#attached']['drupalSettings']['contextual']['linkDefinitionsHash'] = hash('sha256', Settings::getHashSalt() . implode(':', array_keys($cl_definitions)));
 }
 
 /**
diff --git a/core/modules/contextual/js/contextual.js b/core/modules/contextual/js/contextual.js
index 39daa09973..be955d324d 100644
--- a/core/modules/contextual/js/contextual.js
+++ b/core/modules/contextual/js/contextual.js
@@ -15,8 +15,10 @@
 
   var cachedPermissionsHash = storage.getItem('Drupal.contextual.permissionsHash');
   var permissionsHash = drupalSettings.user.permissionsHash;
-  if (cachedPermissionsHash !== permissionsHash) {
-    if (typeof permissionsHash === 'string') {
+  var cachedDefinitionsHash = storage.getItem('Drupal.contextual.linkDefinitionsHash');
+  var linkDefinitionsHash = drupalSettings.contextual.linkDefinitionsHash;
+  if (cachedPermissionsHash !== permissionsHash || cachedDefinitionsHash !== linkDefinitionsHash) {
+    if (typeof permissionsHash === 'string' && typeof linkDefinitionsHash === 'string') {
       _.chain(storage).keys().each(function (key) {
         if (key.substring(0, 18) === 'Drupal.contextual.') {
           storage.removeItem(key);
@@ -24,6 +26,7 @@
       });
     }
     storage.setItem('Drupal.contextual.permissionsHash', permissionsHash);
+    storage.setItem('Drupal.contextual.linkDefinitionsHash', linkDefinitionsHash);
   }
 
   function initContextual($contextual, html) {
diff --git a/core/modules/contextual/tests/modules/contextual_test/contextual_test.info.yml b/core/modules/contextual/tests/modules/contextual_test/contextual_test.info.yml
new file mode 100644
index 0000000000..2ec0eadeaa
--- /dev/null
+++ b/core/modules/contextual/tests/modules/contextual_test/contextual_test.info.yml
@@ -0,0 +1,9 @@
+name: 'Contextual tests'
+type: module
+description: 'Provides conextual test links.'
+package: Testing
+version: VERSION
+core: 8.x
+dependencies:
+  - block
+  - contextual
diff --git a/core/modules/contextual/tests/modules/contextual_test/contextual_test.links.contextual.yml b/core/modules/contextual/tests/modules/contextual_test/contextual_test.links.contextual.yml
new file mode 100644
index 0000000000..f7b245508b
--- /dev/null
+++ b/core/modules/contextual/tests/modules/contextual_test/contextual_test.links.contextual.yml
@@ -0,0 +1,4 @@
+contextual_test_link:
+  title: 'Another block link'
+  route_name: 'entity.block.edit_form'
+  group: 'block'
diff --git a/core/modules/contextual/tests/src/FunctionalJavascript/ContextualLinksTest.php b/core/modules/contextual/tests/src/FunctionalJavascript/ContextualLinksTest.php
index 12ce10db8b..0115556350 100644
--- a/core/modules/contextual/tests/src/FunctionalJavascript/ContextualLinksTest.php
+++ b/core/modules/contextual/tests/src/FunctionalJavascript/ContextualLinksTest.php
@@ -35,13 +35,13 @@ public function testContextualLinksVisibility() {
     ]));
 
     $this->drupalGet('user');
-    $contextualLinks = $this->assertSession()->waitForElement('css', '.contextual button');
-    $this->assertEmpty($contextualLinks);
+    $contextualButton = $this->assertSession()->waitForElement('css', '.contextual button');
+    $this->assertEmpty($contextualButton);
 
     // Ensure visibility remains correct after cached paged load.
     $this->drupalGet('user');
-    $contextualLinks = $this->assertSession()->waitForElement('css', '.contextual button');
-    $this->assertEmpty($contextualLinks);
+    $contextualButton = $this->assertSession()->waitForElement('css', '.contextual button');
+    $this->assertEmpty($contextualButton);
 
     // Grant permissions to use contextual links on blocks.
     $this->grantPermissions(Role::load(Role::AUTHENTICATED_ID), [
@@ -50,13 +50,33 @@ public function testContextualLinksVisibility() {
     ]);
 
     $this->drupalGet('user');
-    $contextualLinks = $this->assertSession()->waitForElement('css', '.contextual button');
-    $this->assertNotEmpty($contextualLinks);
+    $contextualButton = $this->assertSession()->waitForElement('css', '.contextual button');
+    $this->assertNotEmpty($contextualButton);
+    $this->assertContextualLinkWithClass('block-configure');
 
     // Ensure visibility remains correct after cached paged load.
     $this->drupalGet('user');
-    $contextualLinks = $this->assertSession()->waitForElement('css', '.contextual button');
-    $this->assertNotEmpty($contextualLinks);
+    $contextualButton = $this->assertSession()->waitForElement('css', '.contextual button');
+    $this->assertNotEmpty($contextualButton);
+    $this->assertContextualLinkWithClass('block-configure');
+
+    $this->container->get('module_installer')->install(['contextual_test']);
+    $this->drupalGet('user');
+    $contextualButton = $this->assertSession()->waitForElement('css', '.contextual button');
+    $this->assertNotEmpty($contextualButton);
+    $this->assertContextualLinkWithClass('block-configure');
+    $this->assertContextualLinkWithClass('contextual-test-link');
+  }
+
+  /**
+   * Assert that contextual link with with a class exists.
+   *
+   * @param string $class
+   *   The class for the li element surrounding the link.
+   */
+  protected function assertContextualLinkWithClass($class) {
+    $contextLink = $this->assertSession()->waitForElement('css', ".contextual-links li.$class a");
+    $this->assertNotEmpty($contextLink);
   }
 
 }
