diff --git a/core/core.services.yml b/core/core.services.yml
index 65882af..ec9dcf9 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -177,6 +177,9 @@ services:
   plugin.manager.menu.local_task:
     class: Drupal\Core\Menu\LocalTaskManager
     arguments: ['@container.namespaces', '@controller_resolver', '@request', '@router.route_provider', '@module_handler', '@cache.cache', '@language_manager']
+  plugin.manager.menu.contextual_links
+    class: Drupal\Core\Menu\ContextualLinkManager
+    arguments: ['@controller_resolver', '@module_handler', '@cache.cache', '@language_manager']
   request:
     class: Symfony\Component\HttpFoundation\Request
     # @TODO the synthetic setting must be uncommented whenever drupal_session_initialize()
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 87b3b18..4879cae 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -1697,7 +1697,7 @@ function theme_links($variables) {
       }
 
       // Handle links.
-      if (isset($link['href'])) {
+      if (isset($link['href']) || isset($link['route_name'])) {
         $is_current_path = ($link['href'] == current_path() || ($link['href'] == '<front>' && drupal_is_front_page()));
         $is_current_language = (empty($link['language']) || $link['language']->id == $language_url->id);
         if ($is_current_path && $is_current_language) {
@@ -1718,8 +1718,13 @@ function theme_links($variables) {
           $item = drupal_render($link_element);
         }
         else {
-          // Pass in $link as $options, they share the same keys.
-          $item = l($link['title'], $link['href'], $link);
+          if (isset($link['href'])) {
+            // Pass in $link as $options, as they share the same keys.
+            $item = l($link['title'], $link['href'], $link);
+          }
+          else {
+            $item = \Drupal::linkGenerator()->generate($link['title'], $link['route_name'], isset($link['route_parameters']) ? $link['route_parameters'] : array(), $link);
+          }
         }
       }
       // Handle title-only text items.
diff --git a/core/lib/Drupal/Core/Menu/ContextualLinkBase.php b/core/lib/Drupal/Core/Menu/ContextualLinkBase.php
new file mode 100644
index 0000000..454f7b0
--- /dev/null
+++ b/core/lib/Drupal/Core/Menu/ContextualLinkBase.php
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Menu\ContextualLinkBase.
+ */
+
+namespace Drupal\Core\Menu;
+
+use Drupal\Component\Plugin\PluginBase;
+
+/**
+ * Provides a common base implementation of a contextual link.
+ */
+class ContextualLinkBase extends PluginBase implements ContextualLinkInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTitle() {
+    return $this->pluginDefinition['title'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRouteName() {
+    return $this->pluginDefinition['route_name'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getGroup() {
+    return $this->pluginDefinition['group'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOptions() {
+    return $this->pluginDefinition['options'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getWeight() {
+    return $this->pluginDefinition['weight'];
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Menu/ContextualLinkInterface.php b/core/lib/Drupal/Core/Menu/ContextualLinkInterface.php
new file mode 100644
index 0000000..59f0171
--- /dev/null
+++ b/core/lib/Drupal/Core/Menu/ContextualLinkInterface.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Menu\ContextualLinkInterface.
+ */
+
+namespace Drupal\Core\Menu;
+
+/**
+ * Defines a contextual link plugin.
+ */
+interface ContextualLinkInterface {
+
+  /**
+   * Returns the localized title to be shown for this contextual link.
+   *
+   * Subclasses may add optional arguments like NodeInterface $node = NULL that
+   * will be supplied by the ControllerResolver.
+   *
+   * @return string
+   *   The title to be shown for this action.
+   *
+   * @see \Drupal\Core\Menu\ContextualLinksManager::getTitle()
+   */
+  public function getTitle();
+
+  /**
+   * Returns the route name of the contextual link.
+   *
+   * @return string
+   *   The name of the route this contextual link links to.
+   */
+  public function getRouteName();
+
+  /**
+   * Returns the group this contextual link should be rendered on.
+   *
+   * @return string
+   *   The contextual links group name.
+   */
+  public function getGroup();
+
+  /**
+   * Returns the options based to the link generator.
+   *
+   * @return array
+   *   The options as expected by LinKGeneratorInterface::generate()
+   */
+  public function getOptions();
+
+  /**
+   * Returns the weight of the contextual link.
+   */
+  public function getWeight();
+
+}
diff --git a/core/lib/Drupal/Core/Menu/ContextualLinkManager.php b/core/lib/Drupal/Core/Menu/ContextualLinkManager.php
new file mode 100644
index 0000000..f51a2b7
--- /dev/null
+++ b/core/lib/Drupal/Core/Menu/ContextualLinkManager.php
@@ -0,0 +1,116 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Menu\ContextualLinkManager.
+ */
+
+namespace Drupal\Core\Menu;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Controller\ControllerResolverInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Language\LanguageManager;
+use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
+use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
+use Drupal\Core\Plugin\Discovery\YamlDiscovery;
+use Drupal\Core\Plugin\Factory\ContainerFactory;
+
+/**
+ * Defines a plugin manager to deal with contextual links.
+ *
+ * @see \Drupal\Core\Menu\ContextualLinkInterface
+ */
+class ContextualLinkManager extends DefaultPluginManager {
+
+  /**
+   * Provides some default values for contextual links
+   *
+   * @var array
+   */
+  protected $defaults = array(
+    'class' => '\Drupal\Core\Menu\ContextualLinkBase',
+    'group' => '',
+    'id' => '',
+    'options' => array(),
+    'route_name' => '',
+    'title' => '',
+    'weight' => 0,
+  );
+
+  /**
+   * Constructs a new ContextualLinksManager instance.
+   *
+   * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
+   *   The controller resolver.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   The cache backend.
+   * @param \Drupal\Core\Language\LanguageManager $language_manager
+   *   The language manager.
+   */
+  public function __construct(ControllerResolverInterface $controller_resolver, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, LanguageManager $language_manager) {
+    $this->discovery = new YamlDiscovery('contextual_links', $module_handler->getModuleDirectories());
+    $this->discovery = new ContainerDerivativeDiscoveryDecorator($this->discovery);
+    $this->factory = new ContainerFactory($this);
+
+    $this->controllerResolver = $controller_resolver;
+    $this->alterInfo($module_handler, 'contextual_links');
+    $this->setCacheBackend($cache_backend, $language_manager, 'contextual_links_plugins');
+  }
+
+  /**
+   * Gets the contextual link plugins by contextual link group.
+   *
+   * @param string $group_name
+   *   The group name.
+   *
+   * @return array
+   *   A list of contextual links plugin definitions, which should be shown.
+   */
+  public function getContextualLinkPluginsByGroup($group_name) {
+    if ($cache = $this->cacheBackend->get($this->cacheKey . ':' . $group_name)) {
+      $contextual_links = $cache->data;
+    }
+    else {
+      $contextual_links = array();
+      foreach ($this->getDefinitions() as $plugin_id => $plugin_definition) {
+        if ($plugin_definition['group'] == $group_name) {
+          $contextual_links[$plugin_id] = $plugin_definition;
+        }
+      }
+      $this->cacheBackend->set($this->cacheKey . ':' . $group_name, $contextual_links);
+    }
+    return $contextual_links;
+  }
+
+  /**
+   * Gets the contextual links as array as expected by theme_links.
+   *
+   * @param string $group_name
+   *   The group name.
+   * @param array $route_parameters
+   *   The incoming route parameters.
+   *
+   * @return array
+   *   A list of links as array.
+   */
+  public function getContextualLinksArrayByGroup($group_name, array $route_parameters) {
+    $links = array();
+    foreach ($this->getContextualLinkPluginsByGroup($group_name) as $plugin_id => $plugin_definition) {
+      /** @var $plugin \Drupal\Core\Menu\ContextualLinkInterface */
+      $plugin = $this->createInstance($plugin_id);
+      $links[$plugin_id] = array(
+        'route_name' => $plugin->getRouteName(),
+        'route_parameters' => $route_parameters,
+        'title' => $plugin->getTitle(),
+        'weight' => $plugin->getWeight(),
+        'localized_options' => $plugin->getOptions(),
+      );
+    }
+    return $links;
+  }
+
+}
diff --git a/core/modules/block/block.module b/core/modules/block/block.module
index 18f1c7f..8b8f07d 100644
--- a/core/modules/block/block.module
+++ b/core/modules/block/block.module
@@ -328,7 +328,7 @@ function _block_get_renderable_region($list = array()) {
     // to perform contextual actions on the help block, and the links needlessly
     // draw attention on it.
     if (isset($build[$key]) && !in_array($block->get('plugin'), array('system_help_block', 'system_main_block'))) {
-      $build[$key]['#contextual_links']['block'] = array('admin/structure/block/manage', array($key));
+      $build[$key]['#contextual_links']['block'] = array('admin/structure/block/manage', array('block' => $key));
 
       // If there are any nested contextual links, move them to the top level.
       if (isset($build[$key]['content']['#contextual_links'])) {
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockRenderController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockRenderController.php
index 3457115..75c6834 100644
--- a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockRenderController.php
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockRenderController.php
@@ -23,7 +23,7 @@ protected function alterBuild(array &$build, EntityInterface $entity, EntityDisp
     parent::alterBuild($build, $entity, $display, $view_mode, $langcode);
     // Add contextual links for this custom block.
     if (!empty($entity->id->value) && $view_mode == 'full') {
-      $build['#contextual_links']['custom_block'] = array('block', array($entity->id()));
+      $build['#contextual_links']['custom_block'] = array('block', array('block' => $entity->id()));
     }
   }
 
diff --git a/core/modules/contextual/contextual.module b/core/modules/contextual/contextual.module
index 527cc2b..cd90e40 100644
--- a/core/modules/contextual/contextual.module
+++ b/core/modules/contextual/contextual.module
@@ -256,10 +256,18 @@ function contextual_pre_render_placeholder($element) {
 function contextual_pre_render_links($element) {
   // Retrieve contextual menu links.
   $items = array();
+
+  $contextual_links_manager = \Drupal::service('plugin.manager.menu.contextual_links');
+  foreach ($element['#contextual_links'] as $group => $args) {
+    $res = $contextual_links_manager->getContextualLinksArrayByGroup($group, $args[1]);
+    $items += $res;
+  }
+
   foreach ($element['#contextual_links'] as $module => $args) {
     $items += menu_contextual_links($module, $args[0], $args[1]);
   }
 
+
   // Transform contextual links into parameters suitable for theme_links().
   $links = array();
   foreach ($items as $class => $item) {
@@ -267,6 +275,8 @@ function contextual_pre_render_links($element) {
     $links[$class] = array(
       'title' => $item['title'],
       'href' => $item['href'],
+      'route_name' => isset($item['route_name']) ? $item['route_name'] : '',
+      'route_parameters' => isset($item['route_parameters']) ? $item['route_parameters'] : array(),
     );
     // @todo theme_links() should *really* use the same parameters as l().
     $item['localized_options'] += array('query' => array());
@@ -323,13 +333,14 @@ function _contextual_links_to_id($contextual_links) {
   $id = '';
   foreach ($contextual_links as $module => $args) {
     $parent_path = $args[0];
+    $path_keys = implode('/', array_keys($args[1]));
     $path_args = implode('/', $args[1]);
     $metadata = drupal_http_build_query((isset($args[2])) ? $args[2] : array());
 
     if (drupal_strlen($id) > 0) {
       $id .= '|';
     }
-    $id .= $module . ':' . $parent_path . ':' . $path_args . ':' . $metadata;
+    $id .= $module . ':' . $parent_path . ':' . $path_keys . ':' . $path_args . ':' . $metadata;
   }
   return $id;
 }
@@ -349,8 +360,14 @@ function _contextual_id_to_links($id) {
   $contextual_links = array();
   $contexts = explode('|', $id);
   foreach ($contexts as $context) {
-    list($module, $parent_path, $path_args, $metadata_raw) = explode(':', $context);
+    list($module, $parent_path, $path_keys, $path_args, $metadata_raw) = explode(':', $context);
+    $path_keys = explode('/', $path_keys);
     $path_args = explode('/', $path_args);
+    foreach ($path_keys as $counter => $key) {
+      $arg = $path_args[$counter];
+      unset($path_args[$counter]);
+      $path_args[$key] = $arg;
+    }
     $metadata = array();
     parse_str($metadata_raw, $metadata);
     $contextual_links[$module] = array($parent_path, $path_args, $metadata);
diff --git a/core/modules/contextual/lib/Drupal/contextual/Tests/ContextualDynamicContextTest.php b/core/modules/contextual/lib/Drupal/contextual/Tests/ContextualDynamicContextTest.php
index 3c35e48..c771f0b 100644
--- a/core/modules/contextual/lib/Drupal/contextual/Tests/ContextualDynamicContextTest.php
+++ b/core/modules/contextual/lib/Drupal/contextual/Tests/ContextualDynamicContextTest.php
@@ -60,10 +60,10 @@ function testDifferentPermissions() {
     // Now, on the front page, all article nodes should have contextual links
     // placeholders, as should the view that contains them.
     $ids = array(
-      'node:node:' . $node1->id() . ':',
-      'node:node:' . $node2->id() . ':',
-      'node:node:' . $node3->id() . ':',
-      'views_ui:admin/structure/views/view:frontpage:location=page&name=frontpage&display_id=page_1',
+      'node:node:node:' . $node1->id() . ':',
+      'node:node:node:' . $node2->id() . ':',
+      'node:node:node:' . $node3->id() . ':',
+      'views_ui:admin/structure/views/view:0:frontpage:location=page&name=frontpage&display_id=page_1',
     );
 
     // Editor user: can access contextual links and can edit articles.
diff --git a/core/modules/contextual/lib/Drupal/contextual/Tests/ContextualUnitTest.php b/core/modules/contextual/lib/Drupal/contextual/Tests/ContextualUnitTest.php
index d2018ac..e4c828d 100644
--- a/core/modules/contextual/lib/Drupal/contextual/Tests/ContextualUnitTest.php
+++ b/core/modules/contextual/lib/Drupal/contextual/Tests/ContextualUnitTest.php
@@ -33,11 +33,13 @@ function _contextual_links_id_testcases() {
       'links' => array(
         'node' => array(
           'node',
-          array('14031991'),
+          array(
+            'node' => '14031991',
+          ),
           array()
         ),
       ),
-      'id' => 'node:node:14031991:',
+      'id' => 'node:node:node:14031991:',
     );
 
     // Test branch conditions:
@@ -48,11 +50,15 @@ function _contextual_links_id_testcases() {
       'links' => array(
         'foo' => array(
           'baz/in/ga',
-          array('bar', 'baz', 'qux'),
+          array(
+            'bar',
+            'key' => 'baz',
+            'qux',
+          ),
           array()
         ),
       ),
-      'id' => 'foo:baz/in/ga:bar/baz/qux:',
+      'id' => 'foo:baz/in/ga:0/key/1:bar/baz/qux:',
     );
 
     // Test branch conditions:
@@ -70,7 +76,7 @@ function _contextual_links_id_testcases() {
           )
         ),
       ),
-      'id' => 'views_ui:admin/structure/views/view:frontpage:location=page&display=page_1',
+      'id' => 'views_ui:admin/structure/views/view:0:frontpage:location=page&display=page_1',
     );
 
     // Test branch conditions:
@@ -80,12 +86,18 @@ function _contextual_links_id_testcases() {
       'links' => array(
         'node' => array(
           'node',
-          array('14031991'),
+          array(
+            'node' => '14031991',
+          ),
           array()
         ),
         'foo' => array(
           'baz/in/ga',
-          array('bar', 'baz', 'qux'),
+          array(
+            'bar',
+            'key' => 'baz',
+            'qux',
+          ),
           array()
         ),
         'edge' => array(
@@ -94,7 +106,7 @@ function _contextual_links_id_testcases() {
           array()
         ),
       ),
-      'id' => 'node:node:14031991:|foo:baz/in/ga:bar/baz/qux:|edge:edge:20011988:',
+      'id' => 'node:node:node:14031991:|foo:baz/in/ga:0/key/1:bar/baz/qux:|edge:edge:0:20011988:',
     );
 
     return $tests;
diff --git a/core/modules/menu/menu.module b/core/modules/menu/menu.module
index f564f33..7d709fd 100644
--- a/core/modules/menu/menu.module
+++ b/core/modules/menu/menu.module
@@ -388,7 +388,9 @@ function menu_block_view_system_menu_block_alter(array &$build, BlockPluginInter
   // Add contextual links for system menu blocks.
   if (isset($build['content'])) {
     foreach (element_children($build['content']) as $key) {
-      $build['content']['#contextual_links']['menu'] = array('admin/structure/menu/manage', array($build['content'][$key]['#original_link']['menu_name']));
+      $build['content']['#contextual_links']['menu'] = array('admin/structure/menu/manage', array(
+        'menu' => $build['content'][$key]['#original_link']['menu_name'],
+      ));
     }
   }
 }
diff --git a/core/modules/node/lib/Drupal/node/NodeRenderController.php b/core/modules/node/lib/Drupal/node/NodeRenderController.php
index 6da0b29..75f85a8 100644
--- a/core/modules/node/lib/Drupal/node/NodeRenderController.php
+++ b/core/modules/node/lib/Drupal/node/NodeRenderController.php
@@ -83,7 +83,7 @@ public function buildContent(array $entities, array $displays, $view_mode, $lang
   protected function alterBuild(array &$build, EntityInterface $entity, EntityDisplay $display, $view_mode, $langcode = NULL) {
     parent::alterBuild($build, $entity, $display, $view_mode, $langcode);
     if ($entity->id()) {
-      $build['#contextual_links']['node'] = array('node', array($entity->id()));
+      $build['#contextual_links']['node'] = array('node', array('node' => $entity->id()));
     }
   }
 
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/TermRenderController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TermRenderController.php
index cf319d0..6b0fd2f 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/TermRenderController.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TermRenderController.php
@@ -54,7 +54,9 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langco
   protected function alterBuild(array &$build, EntityInterface $entity, EntityDisplay $display, $view_mode, $langcode = NULL) {
     parent::alterBuild($build, $entity, $display, $view_mode, $langcode);
     $build['#attached']['css'][] = drupal_get_path('module', 'taxonomy') . '/css/taxonomy.module.css';
-    $build['#contextual_links']['taxonomy'] = array('taxonomy/term', array($entity->id()));
+    $build['#contextual_links']['taxonomy'] = array('taxonomy/term', array(
+      'taxonomy_term' => $entity->id(),
+    ));
   }
 
 }
