diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityListController.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityListController.php
index 979dddd..e335b19 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityListController.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityListController.php
@@ -30,8 +30,8 @@ public function load() {
   /**
    * {@inheritdoc}
    */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
+  public function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
     $uri = $entity->uri();
 
     // Ensure the edit operation exists since it is access controlled.
diff --git a/core/lib/Drupal/Core/Entity/EntityListController.php b/core/lib/Drupal/Core/Entity/EntityListController.php
index ef3ec43..cc023bc 100644
--- a/core/lib/Drupal/Core/Entity/EntityListController.php
+++ b/core/lib/Drupal/Core/Entity/EntityListController.php
@@ -93,6 +93,24 @@ protected function getLabel(EntityInterface $entity) {
    * {@inheritdoc}
    */
   public function getOperations(EntityInterface $entity) {
+    $operations = array_merge($this->moduleHandler()->invokeAll('entity_operation', array($entity)), $this->getDefaultOperations($entity));
+    $this->moduleHandler->alter('entity_operation', $operations, $entity);
+    uasort($operations, '\Drupal\Component\Utility\SortArray::sortByWeightElement');
+
+    return $operations;
+  }
+
+  /**
+   * Gets this list's default operations.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity the operations are for.
+   *
+   * @return array
+   *   The array structure is identical to the return value of
+   *   self::getOperations().
+   */
+  protected function getDefaultOperations(EntityInterface $entity) {
     $uri = $entity->uri();
 
     $operations = array();
@@ -157,14 +175,11 @@ public function buildRow(EntityInterface $entity) {
    * @see \Drupal\Core\Entity\EntityListController::render()
    */
   public function buildOperations(EntityInterface $entity) {
-    // Retrieve and sort operations.
-    $operations = $this->getOperations($entity);
-    $this->moduleHandler()->alter('entity_operation', $operations, $entity);
-    uasort($operations, array('Drupal\Component\Utility\SortArray', 'sortByWeightElement'));
     $build = array(
       '#type' => 'operations',
-      '#links' => $operations,
+      '#links' => $this->getOperations($entity),
     );
+
     return $build;
   }
 
diff --git a/core/modules/action/lib/Drupal/action/ActionListController.php b/core/modules/action/lib/Drupal/action/ActionListController.php
index ee612c9..011d47e 100644
--- a/core/modules/action/lib/Drupal/action/ActionListController.php
+++ b/core/modules/action/lib/Drupal/action/ActionListController.php
@@ -99,8 +99,8 @@ public function buildHeader() {
   /**
    * {@inheritdoc}
    */
-  public function getOperations(EntityInterface $entity) {
-    $operations = $entity->isConfigurable() ? parent::getOperations($entity) : array();
+  public function getDefaultOperations(EntityInterface $entity) {
+    $operations = $entity->isConfigurable() ? parent::getDefaultOperations($entity) : array();
     if (isset($operations['edit'])) {
       $operations['edit']['title'] = t('Configure');
     }
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockListController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockListController.php
index efa31b3..645bf6f 100644
--- a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockListController.php
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockListController.php
@@ -34,8 +34,8 @@ public function buildRow(EntityInterface $entity) {
   /**
    * {@inheritdoc}
    */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
+  public function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
     // The custom block edit path does not contain '/edit'.
     if (isset($operations['edit'])) {
       $uri = $entity->uri();
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeListController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeListController.php
index 070171d..0f77790 100644
--- a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeListController.php
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeListController.php
@@ -18,8 +18,8 @@ class CustomBlockTypeListController extends ConfigEntityListController {
   /**
    * {@inheritdoc}
    */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
+  public function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
     // Place the edit operation after the operations added by field_ui.module
     // which have the weights 15, 20, 25.
     if (isset($operations['edit'])) {
diff --git a/core/modules/block/lib/Drupal/block/BlockListController.php b/core/modules/block/lib/Drupal/block/BlockListController.php
index da7cbaa..98e075d 100644
--- a/core/modules/block/lib/Drupal/block/BlockListController.php
+++ b/core/modules/block/lib/Drupal/block/BlockListController.php
@@ -376,8 +376,8 @@ public function buildForm(array $form, array &$form_state) {
   /**
    * {@inheritdoc}
    */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
+  public function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
 
     if (isset($operations['edit'])) {
       $operations['edit']['title'] = t('Configure');
diff --git a/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationEntityListController.php b/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationEntityListController.php
index 8c09840..1a63c88 100644
--- a/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationEntityListController.php
+++ b/core/modules/config_translation/lib/Drupal/config_translation/Controller/ConfigTranslationEntityListController.php
@@ -83,13 +83,13 @@ public function buildHeader() {
   /**
    * {@inheritdoc}
    */
-  public function buildOperations(EntityInterface $entity) {
-    $operations = parent::buildOperations($entity);
-    foreach (array_keys($operations['#links']) as $operation) {
+  public function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
+    foreach (array_keys($operations) as $operation) {
       // This is a translation UI for translators. Show the translation
       // operation only.
       if (!($operation == 'translate')) {
-        unset($operations['#links'][$operation]);
+        unset($operations[$operation]);
       }
     }
     return $operations;
diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module
index 5751c15..cf83924 100644
--- a/core/modules/field_ui/field_ui.module
+++ b/core/modules/field_ui/field_ui.module
@@ -163,9 +163,10 @@ function field_ui_form_node_type_form_alter(&$form, $form_state) {
 }
 
 /**
- * Implements hook_entity_operation_alter().
+ * Implements hook_entity_operation().
  */
-function field_ui_entity_operation_alter(array &$operations, EntityInterface $entity) {
+function field_ui_entity_operation(EntityInterface $entity) {
+  $operations = array();
   $info = $entity->entityInfo();
   // Add manage fields and display links if this entity type is the bundle
   // of another.
@@ -196,6 +197,8 @@ function field_ui_entity_operation_alter(array &$operations, EntityInterface $en
       );
     }
   }
+
+  return $operations;
 }
 
 /**
diff --git a/core/modules/filter/lib/Drupal/filter/FilterFormatListController.php b/core/modules/filter/lib/Drupal/filter/FilterFormatListController.php
index 3b70aea..5b808af 100644
--- a/core/modules/filter/lib/Drupal/filter/FilterFormatListController.php
+++ b/core/modules/filter/lib/Drupal/filter/FilterFormatListController.php
@@ -117,8 +117,8 @@ public function buildRow(EntityInterface $entity) {
   /**
    * {@inheritdoc}
    */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
+  public function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
 
     if (isset($operations['edit'])) {
       $operations['edit']['title'] = t('Configure');
diff --git a/core/modules/image/lib/Drupal/image/ImageStyleListController.php b/core/modules/image/lib/Drupal/image/ImageStyleListController.php
index cce8dcf..31cec5f 100644
--- a/core/modules/image/lib/Drupal/image/ImageStyleListController.php
+++ b/core/modules/image/lib/Drupal/image/ImageStyleListController.php
@@ -74,7 +74,7 @@ public function buildRow(EntityInterface $entity) {
   /**
    * {@inheritdoc}
    */
-  public function getOperations(EntityInterface $entity) {
+  public function getDefaultOperations(EntityInterface $entity) {
     $uri = $entity->uri('edit-form');
     $flush = array(
       'title' => t('Flush'),
@@ -83,7 +83,7 @@ public function getOperations(EntityInterface $entity) {
       'weight' => 200,
     );
 
-    return parent::getOperations($entity) + array('flush' => $flush);
+    return parent::getDefaultOperations($entity) + array('flush' => $flush);
   }
 
   /**
diff --git a/core/modules/language/lib/Drupal/language/LanguageListController.php b/core/modules/language/lib/Drupal/language/LanguageListController.php
index 601d059..8093748 100644
--- a/core/modules/language/lib/Drupal/language/LanguageListController.php
+++ b/core/modules/language/lib/Drupal/language/LanguageListController.php
@@ -41,8 +41,8 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
+  public function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
     $default = language_default();
 
     // Edit and delete path for Languages entities have a different pattern
diff --git a/core/modules/menu/lib/Drupal/menu/MenuListController.php b/core/modules/menu/lib/Drupal/menu/MenuListController.php
index 4c5a408..93f1c95 100644
--- a/core/modules/menu/lib/Drupal/menu/MenuListController.php
+++ b/core/modules/menu/lib/Drupal/menu/MenuListController.php
@@ -41,8 +41,8 @@ public function buildRow(EntityInterface $entity) {
   /**
    * {@inheritdoc}
    */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
+  public function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
     $uri = $entity->uri();
 
     if (isset($operations['edit'])) {
diff --git a/core/modules/node/lib/Drupal/node/NodeListController.php b/core/modules/node/lib/Drupal/node/NodeListController.php
index 122b97f..3f82ad1 100644
--- a/core/modules/node/lib/Drupal/node/NodeListController.php
+++ b/core/modules/node/lib/Drupal/node/NodeListController.php
@@ -121,8 +121,8 @@ public function buildRow(EntityInterface $entity) {
   /**
    * {@inheritdoc}
    */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
+  protected function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
 
     $destination = drupal_get_destination();
     foreach ($operations as $key => $operation) {
diff --git a/core/modules/node/lib/Drupal/node/NodeTypeListController.php b/core/modules/node/lib/Drupal/node/NodeTypeListController.php
index 1549b8f..cd96068 100644
--- a/core/modules/node/lib/Drupal/node/NodeTypeListController.php
+++ b/core/modules/node/lib/Drupal/node/NodeTypeListController.php
@@ -81,8 +81,8 @@ public function buildRow(EntityInterface $entity) {
   /**
    * {@inheritdoc}
    */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
+  public function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
     // Place the edit operation after the operations added by field_ui.module
     // which have the weights 15, 20, 25.
     if (isset($operations['edit'])) {
diff --git a/core/modules/picture/lib/Drupal/picture/PictureMappingListController.php b/core/modules/picture/lib/Drupal/picture/PictureMappingListController.php
index ffe4c0c..38e78ca 100644
--- a/core/modules/picture/lib/Drupal/picture/PictureMappingListController.php
+++ b/core/modules/picture/lib/Drupal/picture/PictureMappingListController.php
@@ -36,8 +36,8 @@ public function buildRow(EntityInterface $entity) {
   /**
    * {@inheritdoc}
    */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
+  public function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
     $uri = $entity->uri();
     $operations['duplicate'] = array(
       'title' => t('Duplicate'),
diff --git a/core/modules/search/lib/Drupal/search/SearchPageListController.php b/core/modules/search/lib/Drupal/search/SearchPageListController.php
index 4586a6b..15d5140 100644
--- a/core/modules/search/lib/Drupal/search/SearchPageListController.php
+++ b/core/modules/search/lib/Drupal/search/SearchPageListController.php
@@ -254,9 +254,9 @@ public function buildForm(array $form, array &$form_state) {
   /**
    * {@inheritdoc}
    */
-  public function getOperations(EntityInterface $entity) {
+  public function getDefaultOperations(EntityInterface $entity) {
     /** @var $entity \Drupal\search\SearchPageInterface */
-    $operations = parent::getOperations($entity);
+    $operations = parent::getDefaultOperations($entity);
 
     // Prevent the default search from being disabled or deleted.
     if ($entity->isDefaultSearch()) {
diff --git a/core/modules/shortcut/lib/Drupal/shortcut/ShortcutSetListController.php b/core/modules/shortcut/lib/Drupal/shortcut/ShortcutSetListController.php
index 179992c..07046a7 100644
--- a/core/modules/shortcut/lib/Drupal/shortcut/ShortcutSetListController.php
+++ b/core/modules/shortcut/lib/Drupal/shortcut/ShortcutSetListController.php
@@ -26,8 +26,8 @@ public function buildHeader() {
   /**
    * {@inheritdoc}
    */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
+  public function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
     $uri = $entity->uri();
 
     if (isset($operations['edit'])) {
diff --git a/core/modules/system/entity.api.php b/core/modules/system/entity.api.php
index 451de06..6e1ee10 100644
--- a/core/modules/system/entity.api.php
+++ b/core/modules/system/entity.api.php
@@ -690,6 +690,27 @@ function hook_entity_field_info_alter(&$info, $entity_type) {
 }
 
 /**
+ * Declares entity operations.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ *   The entity on which the linked operations will be performed.
+ *
+ * @return array
+ *   Operations array as returned by
+ *   \Drupal\Core\Entity\EntityListControllerInterface::getOperations().
+ */
+function hook_entity_operation(\Drupal\Core\Entity\EntityInterface $entity) {
+  $operations = array();
+  $operations['translate'] = array(
+    'title' => t('Translate'),
+    'route_name' => 'foo_module.entity.translate',
+    'weight' => 50,
+  );
+
+  return $operations;
+}
+
+/**
  * Alter entity operations.
  *
  * @param array $operations
@@ -699,11 +720,12 @@ function hook_entity_field_info_alter(&$info, $entity_type) {
  *   The entity on which the linked operations will be performed.
  */
 function hook_entity_operation_alter(array &$operations, \Drupal\Core\Entity\EntityInterface $entity) {
-  $uri = $entity->uri();
+  // Alter the title and weight.
   $operations['translate'] = array(
-    'title' => t('Translate'),
-    'href' => $uri['path'] . '/translate',
-    'weight' => 50,
+    'title' => t('Translate @entity_type', array(
+      '@entity_type' => $entity->entityType(),
+    )),
+    'weight' => 99,
   );
 }
 
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyListController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyListController.php
index ff93ede..d649fc1 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyListController.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyListController.php
@@ -30,8 +30,8 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
+  public function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
     $uri = $entity->uri();
 
     if (isset($operations['edit'])) {
diff --git a/core/modules/user/lib/Drupal/user/Controller/UserListController.php b/core/modules/user/lib/Drupal/user/Controller/UserListController.php
index a928b55..89aa5dd 100644
--- a/core/modules/user/lib/Drupal/user/Controller/UserListController.php
+++ b/core/modules/user/lib/Drupal/user/Controller/UserListController.php
@@ -138,8 +138,8 @@ public function buildRow(EntityInterface $entity) {
   /**
    * {@inheritdoc}
    */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
+  public function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
     if (isset($operations['edit'])) {
       $destination = drupal_get_destination();
       $operations['edit']['query'] = $destination;
diff --git a/core/modules/user/lib/Drupal/user/RoleListController.php b/core/modules/user/lib/Drupal/user/RoleListController.php
index 4aa4f52..e78b7c5 100644
--- a/core/modules/user/lib/Drupal/user/RoleListController.php
+++ b/core/modules/user/lib/Drupal/user/RoleListController.php
@@ -41,8 +41,8 @@ public function buildRow(EntityInterface $entity) {
   /**
    * {@inheritdoc}
    */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
+  public function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
 
     $operations['permissions'] = array(
       'title' => t('Edit permissions'),
diff --git a/core/modules/views_ui/lib/Drupal/views_ui/ViewListController.php b/core/modules/views_ui/lib/Drupal/views_ui/ViewListController.php
index 363a285..b03aa6e 100644
--- a/core/modules/views_ui/lib/Drupal/views_ui/ViewListController.php
+++ b/core/modules/views_ui/lib/Drupal/views_ui/ViewListController.php
@@ -134,8 +134,8 @@ public function buildHeader() {
   /**
    * {@inheritdoc}
    */
-  public function getOperations(EntityInterface $entity) {
-    $operations = parent::getOperations($entity);
+  public function getDefaultOperations(EntityInterface $entity) {
+    $operations = parent::getDefaultOperations($entity);
     $uri = $entity->uri();
 
     $operations['clone'] = array(
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityListControllerTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityListControllerTest.php
index 6db04b5..573a16c 100644
--- a/core/tests/Drupal/Tests/Core/Entity/EntityListControllerTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityListControllerTest.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\Tests\Core\Entity;
 
+use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityListController;
 use Drupal\entity_test\EntityTestListController;
 use Drupal\Tests\UnitTestCase;
 
@@ -21,6 +23,13 @@
 class EntityListControllerTest extends UnitTestCase {
 
   /**
+   * The service container used for testing.
+   *
+   * @var \Drupal\Core\DependencyInjection\ContainerBuilder
+   */
+  protected $container;
+
+  /**
    * The entity used to construct the EntityListController.
    *
    * @var \Drupal\user\RoleInterface|\PHPUnit_Framework_MockObject_MockObject
@@ -34,6 +43,34 @@ class EntityListControllerTest extends UnitTestCase {
    */
   protected $entityListController;
 
+  /**
+   * The entity type used for testing.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $entityType;
+
+  /**
+   * The module handler used for testing.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $moduleHandler;
+
+  /**
+   * The translation manager.
+   *
+   * @var \Drupal\Core\StringTranslation\TranslationInterface
+   */
+  protected $translationManager;
+
+  /**
+   * The role storage used for testing.
+   *
+   * @var \Drupal\user\RoleStorageControllerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $roleStorage;
+
   public static function getInfo() {
     return array(
       'name' => 'Entity list controller test',
@@ -49,10 +86,49 @@ protected function setUp() {
     parent::setUp();
 
     $this->role = $this->getMock('Drupal\user\RoleInterface');
-    $role_storage_controller = $this->getMock('Drupal\user\RoleStorageControllerInterface');
-    $module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
-    $entity_type = $this->getMock('Drupal\Core\Entity\EntityTypeInterface');
-    $this->entityListController = new TestEntityListController($entity_type, $role_storage_controller, $module_handler);
+    $this->roleStorage = $this->getMock('\Drupal\user\RoleStorageControllerInterface');
+    $this->moduleHandler = $this->getMock('\Drupal\Core\Extension\ModuleHandlerInterface');
+    $this->entityType = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface');
+    $this->translationManager = $this->getMock('\Drupal\Core\StringTranslation\TranslationInterface');
+    $this->entityListController = new TestEntityListController($this->entityType, $this->roleStorage, $this->moduleHandler);
+    $this->container = new ContainerBuilder();
+    \Drupal::setContainer($this->container);
+  }
+
+  /**
+   * @covers \Drupal\Core\Entity\EntityListController::getOperations
+   */
+  public function testGetOperations() {
+    $operations = array(
+      $this->randomName() => array(
+        'title' => $this->randomName(),
+      ),
+    );
+    $this->moduleHandler->expects($this->once())
+      ->method('invokeAll')
+      ->with('entity_operation', array($this->role))
+      ->will($this->returnValue($operations));
+    $this->moduleHandler->expects($this->once())
+      ->method('alter')
+      ->with('entity_operation');
+
+    $this->container->set('module_handler', $this->moduleHandler);
+
+    $this->role->expects($this->any())
+      ->method('access')
+      ->will($this->returnValue(TRUE));
+
+    $list = new EntityListController($this->entityType, $this->roleStorage, $this->moduleHandler);
+    $list->setTranslationManager($this->translationManager);
+
+    $operations = $list->getOperations($this->role);
+    $this->assertInternalType('array', $operations);
+    $this->assertArrayHasKey('edit', $operations);
+    $this->assertInternalType('array', $operations['edit']);
+    $this->assertArrayHasKey('title', $operations['edit']);
+    $this->assertArrayHasKey('delete', $operations);
+    $this->assertInternalType('array', $operations['delete']);
+    $this->assertArrayHasKey('title', $operations['delete']);
   }
 
   /**
