diff --git a/core/lib/Drupal/Core/Action/ActionBase.php b/core/lib/Drupal/Core/Action/ActionBase.php
index cb009c3..6f5e167 100644
--- a/core/lib/Drupal/Core/Action/ActionBase.php
+++ b/core/lib/Drupal/Core/Action/ActionBase.php
@@ -2,7 +2,7 @@
 
 namespace Drupal\Core\Action;
 
-use Drupal\Core\Plugin\PluginBase;
+use Drupal\Core\Executable\ExecutablePluginBase;
 
 /**
  * Provides a base implementation for an Action plugin.
@@ -12,15 +12,24 @@
  * @see \Drupal\Core\Action\ActionInterface
  * @see plugin_api
  */
-abstract class ActionBase extends PluginBase implements ActionInterface {
+abstract class ActionBase extends ExecutablePluginBase implements ActionInterface {
 
   /**
    * {@inheritdoc}
    */
-  public function executeMultiple(array $entities) {
-    foreach ($entities as $entity) {
-      $this->execute($entity);
+  public function executeMultiple($context_name, array $context_values) {
+    foreach ($context_values as $context_value) {
+      $this->prepareExecutionContext([$context_name => $context_value]);
+      $this->execute();
     }
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareExecutionContext(array $contexts) {
+    $mapping = $this->getContextMapping();
+    $this->contextHandler->applyContextMapping($this, $contexts, $mapping);
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Action/ActionInterface.php b/core/lib/Drupal/Core/Action/ActionInterface.php
index 5158f96..e5e2a2c 100644
--- a/core/lib/Drupal/Core/Action/ActionInterface.php
+++ b/core/lib/Drupal/Core/Action/ActionInterface.php
@@ -4,18 +4,25 @@
 
 use Drupal\Component\Plugin\PluginInspectionInterface;
 use Drupal\Core\Executable\ExecutableInterface;
+use Drupal\Core\Plugin\ContextAwarePluginInterface;
 use Drupal\Core\Session\AccountInterface;
 
 /**
  * Provides an interface for an Action plugin.
  *
+ * Action plugins are context-aware and configurable. They support the
+ * following keys in their plugin definitions:
+ * - context: An array of context definitions, keyed by context name. Each
+ *   context definition describes the data type and other properties of the
+ *   context. Check the context definition docs for details.
+ * - configuration: An array of configuration option definitions, keyed by
+ *   option name. Each option definition is a typed data definition describing
+ *   the configuration option. Check the typed data definition docs for details.
+ *
  * @todo WARNING: The action API is going to receive some additions before
  * release. The following additions are likely to happen:
  *  - The way configuration is handled and configuration forms are built is
  *    likely to change in order for the plugin to be of use for Rules.
- *  - Actions are going to become context-aware in
- *    https://www.drupal.org/node/2011038, what will deprecated the 'type'
- *    annotation.
  *  - Instead of action implementations saving entities, support for marking
  *    required context as to be saved by the execution manager will be added as
  *    part of https://www.drupal.org/node/2347017.
@@ -28,21 +35,33 @@
  * @see \Drupal\Core\Action\ActionBase
  * @see plugin_api
  */
-interface ActionInterface extends ExecutableInterface, PluginInspectionInterface {
+interface ActionInterface extends ExecutableInterface, ContextAwarePluginInterface, PluginInspectionInterface {
 
   /**
-   * Executes the plugin for an array of objects.
+   * Executes the action for multiple context values.
+   *
+   * This method can be used for executing the action multiple times,
+   * while allowing the action implementation to perform optimizations.
+   * If the implementation provides no optimizations, the fallback behaviour
+   * is to execute the action once per value.
    *
-   * @param array $objects
-   *   An array of entities.
+   * If an action requires more than one context value, other non-multiple
+   * contexts have to be set as usual before this method is invoked.
+   *
+   * @param string $context_name
+   *   The name of the context with multiple values.
+   * @param array $context_values
+   *   An array of context values, for which the action will be executed once
+   *   per value.
    */
-  public function executeMultiple(array $objects);
+  public function executeMultiple($context_name, array $context_values);
 
   /**
-   * Checks object access.
+   * Checks execution access for the set context.
+   *
+   * The caller has to provide all required context, before this method is
+   * invoked.
    *
-   * @param mixed $object
-   *   The object to execute the action on.
    * @param \Drupal\Core\Session\AccountInterface $account
    *   (optional) The user for which to check access, or NULL to check access
    *   for the current user. Defaults to NULL.
@@ -56,6 +75,6 @@ public function executeMultiple(array $objects);
    *   returned, i.e. TRUE means access is explicitly allowed, FALSE means
    *   access is either explicitly forbidden or "no opinion".
    */
-  public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE);
+  public function access(AccountInterface $account = NULL, $return_as_object = FALSE);
 
 }
diff --git a/core/lib/Drupal/Core/Action/ActionManager.php b/core/lib/Drupal/Core/Action/ActionManager.php
index fda253b..ada7207 100644
--- a/core/lib/Drupal/Core/Action/ActionManager.php
+++ b/core/lib/Drupal/Core/Action/ActionManager.php
@@ -2,10 +2,12 @@
 
 namespace Drupal\Core\Action;
 
-use Drupal\Component\Plugin\CategorizingPluginManagerInterface;
+use Drupal\Component\Plugin\ConfigurablePluginInterface;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Plugin\CategorizingPluginManagerTrait;
+use Drupal\Core\Plugin\Context\ContextAwarePluginManagerTrait;
+use Drupal\Core\Plugin\Context\ContextHandlerInterface;
 use Drupal\Core\Plugin\DefaultPluginManager;
 
 /**
@@ -16,9 +18,17 @@
  * @see \Drupal\Core\Action\ActionBase
  * @see plugin_api
  */
-class ActionManager extends DefaultPluginManager implements CategorizingPluginManagerInterface {
+class ActionManager extends DefaultPluginManager implements ActionManagerInterface {
 
   use CategorizingPluginManagerTrait;
+  use ContextAwarePluginManagerTrait;
+
+  /**
+   * The context handler.
+   *
+   * @var \Drupal\Core\Plugin\Context\ContextHandlerInterface
+   */
+  protected $contextHandler;
 
   /**
    * Constructs a new class instance.
@@ -30,26 +40,14 @@ class ActionManager extends DefaultPluginManager implements CategorizingPluginMa
    *   Cache backend instance to use.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler to invoke the alter hook with.
+   * @param \Drupal\Core\Plugin\Context\ContextHandlerInterface $context_handler
+   *   The context handler to use.
    */
-  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
+  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, ContextHandlerInterface $context_handler) {
     parent::__construct('Plugin/Action', $namespaces, $module_handler, 'Drupal\Core\Action\ActionInterface', 'Drupal\Core\Annotation\Action');
     $this->alterInfo('action_info');
     $this->setCacheBackend($cache_backend, 'action_info');
-  }
-
-  /**
-   * Gets the plugin definitions for this entity type.
-   *
-   * @param string $type
-   *   The entity type name.
-   *
-   * @return array
-   *   An array of plugin definitions for this entity type.
-   */
-  public function getDefinitionsByType($type) {
-    return array_filter($this->getDefinitions(), function ($definition) use ($type) {
-      return $definition['type'] === $type;
-    });
+    $this->contextHandler = $context_handler;
   }
 
 }
diff --git a/core/lib/Drupal/Core/Action/ConfigurableActionBase.php b/core/lib/Drupal/Core/Action/ConfigurableActionBase.php
index 3d372fe..b46f7f6 100644
--- a/core/lib/Drupal/Core/Action/ConfigurableActionBase.php
+++ b/core/lib/Drupal/Core/Action/ConfigurableActionBase.php
@@ -23,6 +23,22 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition
   /**
    * {@inheritdoc}
    */
+  public function prepareExecutionContext(array $contexts) {
+    parent::prepareExecutionContext($contexts);
+
+    // Set any pre-configured context values if there is no mapping.
+    $configuration = $this->getConfiguration();
+    $mapping = $this->getContextMapping();
+    foreach ($this->getContextDefinitions() as $context_name => $definition) {
+      if (!isset($mapping[$context_name]) && isset($configuration[$context_name])) {
+        $this->setContextValue($context_name, $configuration[$context_name]);
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function defaultConfiguration() {
     return [];
   }
diff --git a/core/lib/Drupal/Core/Annotation/Action.php b/core/lib/Drupal/Core/Annotation/Action.php
index 94781c7..8944ff4 100644
--- a/core/lib/Drupal/Core/Annotation/Action.php
+++ b/core/lib/Drupal/Core/Annotation/Action.php
@@ -46,13 +46,11 @@ class Action extends Plugin {
   public $confirm_form_route_name = '';
 
   /**
-   * The entity type the action can apply to.
+   * An array of context definitions.
    *
-   * @todo Replace with \Drupal\Core\Plugin\Context\Context.
-   *
-   * @var string
+   * @var array
    */
-  public $type = '';
+  public $context = [];
 
   /**
    * The category under which the action should be listed in the UI.
diff --git a/core/modules/action/src/ActionAddForm.php b/core/modules/action/src/ActionAddForm.php
index 25aaebc..8d1c195 100644
--- a/core/modules/action/src/ActionAddForm.php
+++ b/core/modules/action/src/ActionAddForm.php
@@ -59,7 +59,6 @@ public function buildForm(array $form, FormStateInterface $form_state, $action_i
         $this->entity->setPlugin($id);
         // Derive the label and type from the action definition.
         $this->entity->set('label', $definition['label']);
-        $this->entity->set('type', $definition['type']);
         break;
       }
     }
diff --git a/core/modules/action/src/ActionListBuilder.php b/core/modules/action/src/ActionListBuilder.php
index 2167d8e..c2f3d60 100644
--- a/core/modules/action/src/ActionListBuilder.php
+++ b/core/modules/action/src/ActionListBuilder.php
@@ -75,7 +75,13 @@ public function load() {
    */
   public function buildRow(EntityInterface $entity) {
     $row['type'] = $entity->getType();
-    $row['label'] = $entity->label();
+    // Concatenate all context labels of the action.
+    $contexts = $entity->getPlugin()->getContextDefinitions();
+    array_walk($contexts, function (&$value) {
+      $value = $value->getLabel();
+    });
+    $row['context'] = implode(', ', $contexts);
+    $row['label'] = $this->getLabel($entity);
     if ($this->hasConfigurableActions) {
       $row += parent::buildRow($entity);
     }
@@ -87,7 +93,7 @@ public function buildRow(EntityInterface $entity) {
    */
   public function buildHeader() {
     $header = [
-      'type' => t('Action type'),
+      'context' => t('Action context'),
       'label' => t('Label'),
     ] + parent::buildHeader();
     return $header;
diff --git a/core/modules/action/src/Plugin/Action/EmailAction.php b/core/modules/action/src/Plugin/Action/EmailAction.php
index 0f469ec..19c8c04 100644
--- a/core/modules/action/src/Plugin/Action/EmailAction.php
+++ b/core/modules/action/src/Plugin/Action/EmailAction.php
@@ -22,7 +22,25 @@
  * @Action(
  *   id = "action_send_email_action",
  *   label = @Translation("Send email"),
- *   type = "system"
+ *   context = {
+ *     "entity" = @ContextDefinition("entity",
+ *       label = @Translation("Entity"),
+ *       description = @Translation("The contextual entity that can be used for token replacements."),
+ *       required = false
+ *     ),
+ *     "recipient" = @ContextDefinition("email",
+ *       label = @Translation("Recipient"),
+ *       description = @Translation("The email address to which the message should be sent.")
+ *     ),
+ *     "subject" = @ContextDefinition("string",
+ *       label = @Translation("Subject"),
+ *       description = @Translation("The subject of the message.")
+ *     ),
+ *     "message" = @ContextDefinition("string",
+ *       label = @Translation("Message"),
+ *       description = @Translation("The message that should be sent.")
+ *     ),
+ *   }
  * )
  */
 class EmailAction extends ConfigurableActionBase implements ContainerFactoryPluginInterface {
@@ -119,12 +137,10 @@ public static function create(ContainerInterface $container, array $configuratio
   /**
    * {@inheritdoc}
    */
-  public function execute($entity = NULL) {
-    if (empty($this->configuration['node'])) {
-      $this->configuration['node'] = $entity;
-    }
+  public function execute() {
+    $this->mapConfigurationToContext();
 
-    $recipient = PlainTextOutput::renderFromHtml($this->token->replace($this->configuration['recipient'], $this->configuration));
+    $recipient = $this->getContextValue('recipient');
 
     // If the recipient is a registered user with a language preference, use
     // the recipient's preferred language. Otherwise, use the system default
@@ -137,7 +153,11 @@ public function execute($entity = NULL) {
     else {
       $langcode = $this->languageManager->getDefaultLanguage()->getId();
     }
-    $params = ['context' => $this->configuration];
+
+    $params = ['context' => [
+      'subject' => $this->getContextValue('subject'),
+      'message' => $this->getContextValue('message'),
+    ]];
 
     if ($this->mailManager->mail('system', 'action_send_email', $recipient, $langcode, $params)) {
       $this->logger->notice('Sent email to %recipient', ['%recipient' => $recipient]);
@@ -148,6 +168,37 @@ public function execute($entity = NULL) {
   }
 
   /**
+   * Maps configuration values to the context values if they are not set.
+   *
+   * @todo The configuration options should be removed here and the action
+   * should solely depend on context.
+   */
+  protected function mapConfigurationToContext() {
+    $entity = $this->getContextValue('entity');
+    if (empty($this->configuration['node']) && $entity) {
+      $this->configuration['node'] = $entity;
+    }
+
+    // First check if a recipient context value is set, otherwise use the value
+    // from the configuration.
+    $recipient = $this->getContextValue('recipient');
+    if (!$recipient) {
+      $recipient = $this->configuration['recipient'];
+      $recipient = $this->token->replace($recipient, $this->configuration);
+      $this->setContextValue('recipient', $recipient);
+    }
+
+    $subject = $this->getContextValue('subject');
+    if (!$subject) {
+      $this->setContextValue('subject', $this->configuration['subject']);
+    }
+    $message = $this->getContextValue('message');
+    if (!$message) {
+      $this->setContextValue('message', $this->configuration['message']);
+    }
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function defaultConfiguration() {
diff --git a/core/modules/action/src/Plugin/Action/GotoAction.php b/core/modules/action/src/Plugin/Action/GotoAction.php
index 44e871b..601fff4 100644
--- a/core/modules/action/src/Plugin/Action/GotoAction.php
+++ b/core/modules/action/src/Plugin/Action/GotoAction.php
@@ -20,7 +20,12 @@
  * @Action(
  *   id = "action_goto_action",
  *   label = @Translation("Redirect to URL"),
- *   type = "system"
+ *   context = {
+ *     "url" = @ContextDefinition("string",
+ *       label = @Translation("URL"),
+ *       description = @Translation("The URL to which the user should be redirected. This can be an internal URL like node/1234 or an external URL like https://drupal.org.")
+ *     )
+ *   }
  * )
  */
 class GotoAction extends ConfigurableActionBase implements ContainerFactoryPluginInterface {
@@ -70,7 +75,7 @@ public static function create(ContainerInterface $container, array $configuratio
   /**
    * {@inheritdoc}
    */
-  public function execute($object = NULL) {
+  public function execute() {
     $url = $this->configuration['url'];
     // Leave external URLs unchanged, and assemble others as absolute URLs
     // relative to the site's base URL.
diff --git a/core/modules/action/src/Plugin/Action/MessageAction.php b/core/modules/action/src/Plugin/Action/MessageAction.php
index c85d249..4915421 100644
--- a/core/modules/action/src/Plugin/Action/MessageAction.php
+++ b/core/modules/action/src/Plugin/Action/MessageAction.php
@@ -17,7 +17,17 @@
  * @Action(
  *   id = "action_message_action",
  *   label = @Translation("Display a message to the user"),
- *   type = "system"
+ *   context = {
+ *     "node" = @ContextDefinition("entity:node",
+ *       label = @Translation("Node"),
+ *       description = @Translation("The contextual node entity that can be used for token replacements."),
+ *       required = false
+ *     ),
+ *     "message" = @ContextDefinition("string",
+ *       label = @Translation("Message"),
+ *       description = @Translation("The message to be displayed to the current user. You may include placeholders like [node:title], [user:name], and [comment:body] to represent data that will be different each time message is sent. Not all placeholders will be available in all contexts.")
+ *     )
+ *   }
  * )
  */
 class MessageAction extends ConfigurableActionBase implements ContainerFactoryPluginInterface {
@@ -67,9 +77,30 @@ public static function create(ContainerInterface $container, array $configuratio
   /**
    * {@inheritdoc}
    */
-  public function execute($entity = NULL) {
-    if (empty($this->configuration['node'])) {
-      $this->configuration['node'] = $entity;
+  public function execute() {
+    $this->mapConfigurationToContext();
+    drupal_set_message($this->getContextValue('message'));
+  }
+
+  /**
+   * Maps configuration values to the context values if they are not set.
+   *
+   * @todo The configuration options should be removed here and the action
+   * should solely depend on context.
+   */
+  protected function mapConfigurationToContext() {
+    $node = $this->getContextValue('node');
+    if (empty($this->configuration['node']) && $node) {
+      $this->configuration['node'] = $node;
+    }
+
+    // First check if a message context value is set, otherwise use the value
+    // from the configuration.
+    $message = $this->getContextValue('message');
+    if (!$message) {
+      $message = $this->configuration['message'];
+      $message = $this->token->replace(Xss::filterAdmin($message), $this->configuration);
+      $this->setContextValue('message', $message);
     }
     $message = $this->token->replace($this->configuration['message'], $this->configuration);
     $build = [
diff --git a/core/modules/comment/src/Plugin/Action/PublishComment.php b/core/modules/comment/src/Plugin/Action/PublishComment.php
index 14f5605..b0cab6d 100644
--- a/core/modules/comment/src/Plugin/Action/PublishComment.php
+++ b/core/modules/comment/src/Plugin/Action/PublishComment.php
@@ -11,7 +11,11 @@
  * @Action(
  *   id = "comment_publish_action",
  *   label = @Translation("Publish comment"),
- *   type = "comment"
+ *   context = {
+ *     "comment" = @ContextDefinition("entity:comment",
+ *       label = @Translation("Comment")
+ *     )
+ *   }
  * )
  */
 class PublishComment extends ActionBase {
@@ -19,7 +23,8 @@ class PublishComment extends ActionBase {
   /**
    * {@inheritdoc}
    */
-  public function execute($comment = NULL) {
+  public function execute() {
+    $comment = $this->getContextValue('comment');
     $comment->setPublished(TRUE);
     $comment->save();
   }
diff --git a/core/modules/comment/src/Plugin/Action/SaveComment.php b/core/modules/comment/src/Plugin/Action/SaveComment.php
index fdab65d..a6adac2 100644
--- a/core/modules/comment/src/Plugin/Action/SaveComment.php
+++ b/core/modules/comment/src/Plugin/Action/SaveComment.php
@@ -11,7 +11,11 @@
  * @Action(
  *   id = "comment_save_action",
  *   label = @Translation("Save comment"),
- *   type = "comment"
+ *   context = {
+ *     "comment" = @ContextDefinition("entity:comment",
+ *       label = @Translation("Comment")
+ *     )
+ *   }
  * )
  */
 class SaveComment extends ActionBase {
@@ -19,7 +23,8 @@ class SaveComment extends ActionBase {
   /**
    * {@inheritdoc}
    */
-  public function execute($comment = NULL) {
+  public function execute() {
+    $comment = $this->getContextValue('comment');
     $comment->save();
   }
 
diff --git a/core/modules/comment/src/Plugin/Action/UnpublishByKeywordComment.php b/core/modules/comment/src/Plugin/Action/UnpublishByKeywordComment.php
index ec1b268..a187c15 100644
--- a/core/modules/comment/src/Plugin/Action/UnpublishByKeywordComment.php
+++ b/core/modules/comment/src/Plugin/Action/UnpublishByKeywordComment.php
@@ -17,7 +17,16 @@
  * @Action(
  *   id = "comment_unpublish_by_keyword_action",
  *   label = @Translation("Unpublish comment containing keyword(s)"),
- *   type = "comment"
+ *   context = {
+ *     "comment" = @ContextDefinition("entity:comment",
+ *       label = @Translation("Comment")
+ *     ),
+ *     "keywords" = @ContextDefinition("string",
+ *       label = @Translation("Keywords"),
+ *       description = @Translation("The comment will be unpublished if it contains any of the phrases. Use a case-sensitive, comma-separated list of phrases. Example: funny, bungee jumping, ""Company, Inc."""),
+ *       multiple = true
+ *     )
+ *   }
  * )
  */
 class UnpublishByKeywordComment extends ConfigurableActionBase implements ContainerFactoryPluginInterface {
@@ -72,8 +81,13 @@ public static function create(ContainerInterface $container, array $configuratio
 
   /**
    * {@inheritdoc}
+   *
+   * @todo The configuration options should be removed here and the action
+   * should solely depend on context.
    */
-  public function execute($comment = NULL) {
+  public function execute() {
+    $this->mapConfigurationToContext();
+    $comment = $this->getContextValue('comment');
     $build = $this->viewBuilder->view($comment);
     $text = $this->renderer->renderPlain($build);
     foreach ($this->configuration['keywords'] as $keyword) {
diff --git a/core/modules/comment/src/Plugin/Action/UnpublishComment.php b/core/modules/comment/src/Plugin/Action/UnpublishComment.php
index 6accccc..475de27 100644
--- a/core/modules/comment/src/Plugin/Action/UnpublishComment.php
+++ b/core/modules/comment/src/Plugin/Action/UnpublishComment.php
@@ -11,7 +11,11 @@
  * @Action(
  *   id = "comment_unpublish_action",
  *   label = @Translation("Unpublish comment"),
- *   type = "comment"
+ *   context = {
+ *     "comment" = @ContextDefinition("entity:comment",
+ *       label = @Translation("Comment")
+ *     )
+ *   }
  * )
  */
 class UnpublishComment extends ActionBase {
@@ -19,7 +23,8 @@ class UnpublishComment extends ActionBase {
   /**
    * {@inheritdoc}
    */
-  public function execute($comment = NULL) {
+  public function execute() {
+    $comment = $this->getContextValue('comment');
     $comment->setPublished(FALSE);
     $comment->save();
   }
diff --git a/core/modules/comment/src/Tests/CommentActionsTest.php b/core/modules/comment/src/Tests/CommentActionsTest.php
index 56d34e4..ae27b84 100644
--- a/core/modules/comment/src/Tests/CommentActionsTest.php
+++ b/core/modules/comment/src/Tests/CommentActionsTest.php
@@ -30,12 +30,16 @@ public function testCommentPublishUnpublishActions() {
 
     // Unpublish a comment.
     $action = Action::load('comment_unpublish_action');
-    $action->execute([$comment]);
+    $action_plugin = $action->getPlugin();
+    $action_plugin->setContextValue('comment', $comment);
+    $action_plugin->execute();
     $this->assertTrue($comment->isPublished() === FALSE, 'Comment was unpublished');
 
     // Publish a comment.
     $action = Action::load('comment_publish_action');
-    $action->execute([$comment]);
+    $action_plugin = $action->getPlugin();
+    $action_plugin->setContextValue('comment', $comment);
+    $action_plugin->execute();
     $this->assertTrue($comment->isPublished() === TRUE, 'Comment was published');
   }
 
@@ -64,7 +68,9 @@ public function testCommentUnpublishByKeyword() {
 
     $this->assertTrue($comment->isPublished() === TRUE, 'The comment status was set to published.');
 
-    $action->execute([$comment]);
+    $action_plugin = $action->getPlugin();
+    $action_plugin->setContextValue('comment', $comment);
+    $action_plugin->execute();
     $this->assertTrue($comment->isPublished() === FALSE, 'The comment status was set to not published.');
   }
 
diff --git a/core/modules/node/src/Plugin/Action/AssignOwnerNode.php b/core/modules/node/src/Plugin/Action/AssignOwnerNode.php
index d9a9a43..22fbc09 100644
--- a/core/modules/node/src/Plugin/Action/AssignOwnerNode.php
+++ b/core/modules/node/src/Plugin/Action/AssignOwnerNode.php
@@ -16,7 +16,15 @@
  * @Action(
  *   id = "node_assign_owner_action",
  *   label = @Translation("Change the author of content"),
- *   type = "node"
+ *   context = {
+ *     "node" = @ContextDefinition("entity:node",
+ *       label = @Translation("Node")
+ *     ),
+ *     "user" = @ContextDefinition("entity:user",
+ *       label = @Translation("User"),
+ *       description = @Translation("The user to which you would like to assign ownership.")
+ *     )
+ *   }
  * )
  */
 class AssignOwnerNode extends ConfigurableActionBase implements ContainerFactoryPluginInterface {
diff --git a/core/modules/node/src/Plugin/Action/DeleteNode.php b/core/modules/node/src/Plugin/Action/DeleteNode.php
index 5bf5d9e..58beef3 100644
--- a/core/modules/node/src/Plugin/Action/DeleteNode.php
+++ b/core/modules/node/src/Plugin/Action/DeleteNode.php
@@ -14,7 +14,11 @@
  * @Action(
  *   id = "node_delete_action",
  *   label = @Translation("Delete content"),
- *   type = "node",
+ *   context = {
+ *     "node" = @ContextDefinition("entity:node",
+ *       label = @Translation("Node")
+ *     )
+ *   },
  *   confirm_form_route_name = "node.multiple_delete_confirm"
  * )
  */
@@ -71,29 +75,33 @@ public static function create(ContainerInterface $container, array $configuratio
   /**
    * {@inheritdoc}
    */
-  public function executeMultiple(array $entities) {
+  public function executeMultiple($context_name, array $context_values) {
     $info = [];
     /** @var \Drupal\node\NodeInterface $node */
-    foreach ($entities as $node) {
+    foreach ($context_values as $node) {
       $langcode = $node->language()->getId();
       $info[$node->id()][$langcode] = $langcode;
     }
     $this->tempStore->set($this->currentUser->id(), $info);
+
+    $this->tempStore->set($this->currentUser->id(), $info);
   }
 
   /**
    * {@inheritdoc}
    */
-  public function execute($object = NULL) {
-    $this->executeMultiple([$object]);
+  public function execute() {
+    $entity = $this->getContextValue('node');
+    $this->executeMultiple('node', [$entity]);
   }
 
   /**
    * {@inheritdoc}
    */
-  public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+  public function access(AccountInterface $account = NULL, $return_as_object = FALSE) {
     /** @var \Drupal\node\NodeInterface $object */
-    return $object->access('delete', $account, $return_as_object);
+    $entity = $this->getContextValue('node');
+    return $entity->access('delete', $account, $return_as_object);
   }
 
 }
diff --git a/core/modules/node/src/Plugin/Action/DemoteNode.php b/core/modules/node/src/Plugin/Action/DemoteNode.php
index 04644a9..e7a18dc 100644
--- a/core/modules/node/src/Plugin/Action/DemoteNode.php
+++ b/core/modules/node/src/Plugin/Action/DemoteNode.php
@@ -11,7 +11,11 @@
  * @Action(
  *   id = "node_unpromote_action",
  *   label = @Translation("Demote selected content from front page"),
- *   type = "node"
+ *   context = {
+ *     "node" = @ContextDefinition("entity:node",
+ *       label = @Translation("Node")
+ *     )
+ *   }
  * )
  */
 class DemoteNode extends ActionBase {
@@ -19,7 +23,8 @@ class DemoteNode extends ActionBase {
   /**
    * {@inheritdoc}
    */
-  public function execute($entity = NULL) {
+  public function execute() {
+    $entity = $this->getContextValue('node');
     $entity->setPromoted(FALSE);
     $entity->save();
   }
diff --git a/core/modules/node/src/Plugin/Action/PromoteNode.php b/core/modules/node/src/Plugin/Action/PromoteNode.php
index 097c341..cd5111d 100644
--- a/core/modules/node/src/Plugin/Action/PromoteNode.php
+++ b/core/modules/node/src/Plugin/Action/PromoteNode.php
@@ -11,7 +11,11 @@
  * @Action(
  *   id = "node_promote_action",
  *   label = @Translation("Promote selected content to front page"),
- *   type = "node"
+ *   context = {
+ *     "node" = @ContextDefinition("entity:node",
+ *       label = @Translation("Node")
+ *     )
+ *   }
  * )
  */
 class PromoteNode extends ActionBase {
@@ -19,7 +23,8 @@ class PromoteNode extends ActionBase {
   /**
    * {@inheritdoc}
    */
-  public function execute($entity = NULL) {
+  public function execute() {
+    $entity = $this->getContextValue('node');
     $entity->setPromoted(TRUE);
     $entity->save();
   }
diff --git a/core/modules/node/src/Plugin/Action/PublishNode.php b/core/modules/node/src/Plugin/Action/PublishNode.php
index c8d3b5b..1de79cd 100644
--- a/core/modules/node/src/Plugin/Action/PublishNode.php
+++ b/core/modules/node/src/Plugin/Action/PublishNode.php
@@ -11,7 +11,11 @@
  * @Action(
  *   id = "node_publish_action",
  *   label = @Translation("Publish selected content"),
- *   type = "node"
+ *   context = {
+ *     "node" = @ContextDefinition("entity:node",
+ *       label = @Translation("Node")
+ *     )
+ *   }
  * )
  */
 class PublishNode extends ActionBase {
@@ -19,8 +23,10 @@ class PublishNode extends ActionBase {
   /**
    * {@inheritdoc}
    */
-  public function execute($entity = NULL) {
-    $entity->setPublished()->save();
+  public function execute() {
+    $entity = $this->getContextValue('node');
+    $entity->status = NODE_PUBLISHED;
+    $entity->save();
   }
 
   /**
diff --git a/core/modules/node/src/Plugin/Action/SaveNode.php b/core/modules/node/src/Plugin/Action/SaveNode.php
index e358fef..463f23e 100644
--- a/core/modules/node/src/Plugin/Action/SaveNode.php
+++ b/core/modules/node/src/Plugin/Action/SaveNode.php
@@ -11,7 +11,11 @@
  * @Action(
  *   id = "node_save_action",
  *   label = @Translation("Save content"),
- *   type = "node"
+ *   context = {
+ *     "node" = @ContextDefinition("entity:node",
+ *       label = @Translation("Node")
+ *     )
+ *   }
  * )
  */
 class SaveNode extends ActionBase {
@@ -19,7 +23,8 @@ class SaveNode extends ActionBase {
   /**
    * {@inheritdoc}
    */
-  public function execute($entity = NULL) {
+  public function execute() {
+    $entity = $this->getContextValue('node');
     // We need to change at least one value, otherwise the changed timestamp
     // will not be updated.
     $entity->changed = 0;
diff --git a/core/modules/node/src/Plugin/Action/StickyNode.php b/core/modules/node/src/Plugin/Action/StickyNode.php
index 7aff92b..80fb1b7 100644
--- a/core/modules/node/src/Plugin/Action/StickyNode.php
+++ b/core/modules/node/src/Plugin/Action/StickyNode.php
@@ -11,7 +11,11 @@
  * @Action(
  *   id = "node_make_sticky_action",
  *   label = @Translation("Make selected content sticky"),
- *   type = "node"
+ *   context = {
+ *     "node" = @ContextDefinition("entity:node",
+ *       label = @Translation("Node")
+ *     )
+ *   }
  * )
  */
 class StickyNode extends ActionBase {
diff --git a/core/modules/node/src/Plugin/Action/UnpublishByKeywordNode.php b/core/modules/node/src/Plugin/Action/UnpublishByKeywordNode.php
index c008188..4e4b9ef 100644
--- a/core/modules/node/src/Plugin/Action/UnpublishByKeywordNode.php
+++ b/core/modules/node/src/Plugin/Action/UnpublishByKeywordNode.php
@@ -13,16 +13,31 @@
  * @Action(
  *   id = "node_unpublish_by_keyword_action",
  *   label = @Translation("Unpublish content containing keyword(s)"),
- *   type = "node"
+ *   context = {
+ *     "node" = @ContextDefinition("entity:node",
+ *       label = @Translation("Node")
+ *     ),
+ *     "keywords" = @ContextDefinition("string",
+ *       label = @Translation("Keywords"),
+ *       description = @Translation("The content will be unpublished if it contains any of the phrases. Use a case-sensitive, comma-separated list of phrases. Example: funny, bungee jumping, ""Company, Inc."""),
+ *       multiple = true
+ *     )
+ *   }
  * )
  */
 class UnpublishByKeywordNode extends ConfigurableActionBase {
 
   /**
    * {@inheritdoc}
+   *
+   * @todo The configuration options should be removed here and the action
+   * should solely depend on context.
    */
-  public function execute($node = NULL) {
-    foreach ($this->configuration['keywords'] as $keyword) {
+  public function execute() {
+    $node = $this->getContextValue('node');
+    $keywords = $this->getContextValue('keywords');
+
+    foreach ($keywords as $keyword) {
       $elements = node_view(clone $node);
       if (strpos(drupal_render($elements), $keyword) !== FALSE || strpos($node->label(), $keyword) !== FALSE) {
         $node->setPublished(FALSE);
diff --git a/core/modules/node/src/Plugin/Action/UnpublishNode.php b/core/modules/node/src/Plugin/Action/UnpublishNode.php
index ec6209d..0cef41d 100644
--- a/core/modules/node/src/Plugin/Action/UnpublishNode.php
+++ b/core/modules/node/src/Plugin/Action/UnpublishNode.php
@@ -11,7 +11,11 @@
  * @Action(
  *   id = "node_unpublish_action",
  *   label = @Translation("Unpublish selected content"),
- *   type = "node"
+ *   context = {
+ *     "node" = @ContextDefinition("entity:node",
+ *       label = @Translation("Node")
+ *     )
+ *   }
  * )
  */
 class UnpublishNode extends ActionBase {
@@ -19,8 +23,10 @@ class UnpublishNode extends ActionBase {
   /**
    * {@inheritdoc}
    */
-  public function execute($entity = NULL) {
-    $entity->setUnpublished()->save();
+  public function execute() {
+    $entity = $this->getContextValue('node');
+    $entity->status = NODE_NOT_PUBLISHED;
+    $entity->save();
   }
 
   /**
diff --git a/core/modules/node/src/Plugin/Action/UnstickyNode.php b/core/modules/node/src/Plugin/Action/UnstickyNode.php
index 92ccfe2..36a4bf6 100644
--- a/core/modules/node/src/Plugin/Action/UnstickyNode.php
+++ b/core/modules/node/src/Plugin/Action/UnstickyNode.php
@@ -11,7 +11,11 @@
  * @Action(
  *   id = "node_make_unsticky_action",
  *   label = @Translation("Make selected content not sticky"),
- *   type = "node"
+ *   context = {
+ *     "node" = @ContextDefinition("entity:node",
+ *       label = @Translation("Node")
+ *     )
+ *   }
  * )
  */
 class UnstickyNode extends ActionBase {
@@ -19,8 +23,10 @@ class UnstickyNode extends ActionBase {
   /**
    * {@inheritdoc}
    */
-  public function execute($entity = NULL) {
-    $entity->setSticky(FALSE)->save();
+  public function execute() {
+    $entity = $this->getContextValue('node');
+    $entity->sticky = NODE_NOT_STICKY;
+    $entity->save();
   }
 
   /**
diff --git a/core/modules/node/tests/src/Unit/Plugin/views/field/NodeBulkFormTest.php b/core/modules/node/tests/src/Unit/Plugin/views/field/NodeBulkFormTest.php
index eb92f8c..7132a0c 100644
--- a/core/modules/node/tests/src/Unit/Plugin/views/field/NodeBulkFormTest.php
+++ b/core/modules/node/tests/src/Unit/Plugin/views/field/NodeBulkFormTest.php
@@ -3,6 +3,8 @@
 namespace Drupal\Tests\node\Unit\Plugin\views\field;
 
 use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\Plugin\Context\ContextDefinition;
+use Drupal\Core\Plugin\Context\ContextHandler;
 use Drupal\node\Plugin\views\field\NodeBulkForm;
 use Drupal\Tests\UnitTestCase;
 
@@ -25,26 +27,44 @@ protected function tearDown() {
    * Tests the constructor assignment of actions.
    */
   public function testConstructor() {
-    $actions = [];
+    $action_configs = [];
 
     for ($i = 1; $i <= 2; $i++) {
-      $action = $this->getMock('\Drupal\system\ActionConfigEntityInterface');
+      $action = $this->getMock('\Drupal\Core\Action\ActionInterface');
       $action->expects($this->any())
-        ->method('getType')
-        ->will($this->returnValue('node'));
-      $actions[$i] = $action;
+        ->method('getPluginDefinition')
+        ->will($this->returnValue([
+          'context' => [
+            'node' => new ContextDefinition('entity:node')
+          ]
+        ]));
+
+      $action_config = $this->getMock('\Drupal\system\ActionConfigEntityInterface');
+      $action_config->expects($this->any())
+        ->method('getPlugin')
+        ->will($this->returnValue($action));
+      $action_configs[$i] = $action_config;
     }
 
-    $action = $this->getMock('\Drupal\system\ActionConfigEntityInterface');
+    $action = $this->getMock('\Drupal\Core\Action\ActionInterface');
     $action->expects($this->any())
-      ->method('getType')
-      ->will($this->returnValue('user'));
-    $actions[] = $action;
+      ->method('getPluginDefinition')
+      ->will($this->returnValue([
+        'context' => [
+          'user' => new ContextDefinition('entity:user')
+        ]
+      ]));
+
+    $action_config = $this->getMock('\Drupal\system\ActionConfigEntityInterface');
+    $action_config->expects($this->any())
+      ->method('getPlugin')
+      ->will($this->returnValue($action));
+    $action_configs[] = $action_config;
 
     $entity_storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface');
     $entity_storage->expects($this->any())
       ->method('loadMultiple')
-      ->will($this->returnValue($actions));
+      ->will($this->returnValue($action_configs));
 
     $entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
     $entity_manager->expects($this->once())
@@ -84,10 +104,10 @@ public function testConstructor() {
     $definition['title'] = '';
     $options = [];
 
-    $node_bulk_form = new NodeBulkForm([], 'node_bulk_form', $definition, $entity_manager, $language_manager);
+    $node_bulk_form = new NodeBulkForm([], 'node_bulk_form', $definition, $entity_manager, $language_manager, new ContextHandler());
     $node_bulk_form->init($executable, $display, $options);
 
-    $this->assertAttributeEquals(array_slice($actions, 0, -1, TRUE), 'actions', $node_bulk_form);
+    $this->assertAttributeEquals(array_slice($action_configs, 0, -1, TRUE), 'actions', $node_bulk_form);
   }
 
 }
diff --git a/core/modules/system/src/ActionConfigEntityInterface.php b/core/modules/system/src/ActionConfigEntityInterface.php
index 32b6eef..8af2e67 100644
--- a/core/modules/system/src/ActionConfigEntityInterface.php
+++ b/core/modules/system/src/ActionConfigEntityInterface.php
@@ -17,13 +17,6 @@
   public function isConfigurable();
 
   /**
-   * Returns the operation type.
-   *
-   * @return string
-   */
-  public function getType();
-
-  /**
    * Returns the operation plugin.
    *
    * @return \Drupal\Core\Action\ActionInterface
diff --git a/core/modules/system/src/Entity/Action.php b/core/modules/system/src/Entity/Action.php
index a412838..25bfb76 100644
--- a/core/modules/system/src/Entity/Action.php
+++ b/core/modules/system/src/Entity/Action.php
@@ -46,13 +46,6 @@ class Action extends ConfigEntityBase implements ActionConfigEntityInterface, En
   protected $label;
 
   /**
-   * The action type.
-   *
-   * @var string
-   */
-  protected $type;
-
-  /**
    * The configuration of the action.
    *
    * @var array
@@ -118,36 +111,8 @@ public function getPluginDefinition() {
   /**
    * {@inheritdoc}
    */
-  public function execute(array $entities) {
-    return $this->getPlugin()->executeMultiple($entities);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function isConfigurable() {
     return $this->getPlugin() instanceof ConfigurablePluginInterface;
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function getType() {
-    return $this->type;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function sort(ConfigEntityInterface $a, ConfigEntityInterface $b) {
-    /** @var \Drupal\system\ActionConfigEntityInterface $a */
-    /** @var \Drupal\system\ActionConfigEntityInterface $b */
-    $a_type = $a->getType();
-    $b_type = $b->getType();
-    if ($a_type != $b_type) {
-      return strnatcasecmp($a_type, $b_type);
-    }
-    return parent::sort($a, $b);
-  }
-
 }
diff --git a/core/modules/system/src/Plugin/views/field/BulkForm.php b/core/modules/system/src/Plugin/views/field/BulkForm.php
index 65fdc51..f400b7b 100644
--- a/core/modules/system/src/Plugin/views/field/BulkForm.php
+++ b/core/modules/system/src/Plugin/views/field/BulkForm.php
@@ -8,6 +8,7 @@
 use Drupal\Core\Entity\RevisionableInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Action\ActionManagerInterface;
 use Drupal\Core\Routing\RedirectDestinationTrait;
 use Drupal\Core\TypedData\TranslatableInterface;
 use Drupal\views\Entity\Render\EntityTranslationRenderTrait;
@@ -58,6 +59,13 @@ class BulkForm extends FieldPluginBase implements CacheableDependencyInterface {
    */
   protected $languageManager;
 
+ /**
+   * The action manager.
+   *
+   * @var \Drupal\Core\Action\ActionManagerInterface
+   */
+  protected $actionManager;
+
   /**
    * Constructs a new BulkForm object.
    *
@@ -71,13 +79,16 @@ class BulkForm extends FieldPluginBase implements CacheableDependencyInterface {
    *   The entity manager.
    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
    *   The language manager.
+   * @param \Drupal\Core\Action\ActionManagerInterface $action_manager
+   *   The action manager to use.
    */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager) {
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager, ActionManagerInterface $action_manager) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
 
     $this->entityManager = $entity_manager;
     $this->actionStorage = $entity_manager->getStorage('action');
     $this->languageManager = $language_manager;
+    $this->actionManager = $action_manager;
   }
 
   /**
@@ -89,7 +100,8 @@ public static function create(ContainerInterface $container, array $configuratio
       $plugin_id,
       $plugin_definition,
       $container->get('entity.manager'),
-      $container->get('language_manager')
+      $container->get('language_manager'),
+      $container->get('plugin.manager.action')
     );
   }
 
@@ -100,9 +112,12 @@ public function init(ViewExecutable $view, DisplayPluginBase $display, array &$o
     parent::init($view, $display, $options);
 
     $entity_type = $this->getEntityType();
-    // Filter the actions to only include those for this entity type.
-    $this->actions = array_filter($this->actionStorage->loadMultiple(), function ($action) use ($entity_type) {
-      return $action->getType() == $entity_type;
+    $context_handler = $this->contextHandler;
+    $match_context = ['context' => new Context(new ContextDefinition("entity:$entity_type"))];
+    // Filter the actions to only include those that match for this entity type.
+    $this->actions = array_filter($this->actionStorage->loadMultiple(), function ($action) use ($context_handler, $match_context) {
+      $filtered = $context_handler->filterPluginDefinitionsByContexts($match_context, [$action->getPlugin()->getPluginDefinition()]);
+      return !empty($filtered);
     });
   }
 
diff --git a/core/modules/system/tests/modules/action_test/src/Plugin/Action/NoType.php b/core/modules/system/tests/modules/action_test/src/Plugin/Action/NoType.php
index 1fc3993..028e4b9 100644
--- a/core/modules/system/tests/modules/action_test/src/Plugin/Action/NoType.php
+++ b/core/modules/system/tests/modules/action_test/src/Plugin/Action/NoType.php
@@ -7,11 +7,11 @@
 use Drupal\Core\Session\AccountInterface;
 
 /**
- * Provides an operation with no type specified.
+ * Provides an operation with no context specified.
  *
  * @Action(
  *   id = "action_test_no_type",
- *   label = @Translation("An operation with no type specified")
+ *   label = @Translation("An operation with no context specified")
  * )
  */
 class NoType extends ActionBase {
@@ -19,7 +19,7 @@ class NoType extends ActionBase {
   /**
    * {@inheritdoc}
    */
-  public function execute($entity = NULL) {
+  public function execute() {
   }
 
   /**
diff --git a/core/modules/system/tests/modules/action_test/src/Plugin/Action/SaveEntity.php b/core/modules/system/tests/modules/action_test/src/Plugin/Action/SaveEntity.php
index b20f424..3ed78c9 100644
--- a/core/modules/system/tests/modules/action_test/src/Plugin/Action/SaveEntity.php
+++ b/core/modules/system/tests/modules/action_test/src/Plugin/Action/SaveEntity.php
@@ -11,7 +11,11 @@
  * @Action(
  *   id = "action_test_save_entity",
  *   label = @Translation("Saves entities"),
- *   type = "user"
+ *   context = {
+ *     "user" = @ContextDefinition("entity:user",
+ *       label = @Translation("User")
+ *     )
+ *   }
  * )
  */
 class SaveEntity extends ActionBase {
@@ -19,7 +23,8 @@ class SaveEntity extends ActionBase {
   /**
    * {@inheritdoc}
    */
-  public function execute($entity = NULL) {
+  public function execute() {
+    $entity = $this->getContextValue('user');
     $entity->save();
   }
 
diff --git a/core/modules/user/src/Plugin/Action/AddRoleUser.php b/core/modules/user/src/Plugin/Action/AddRoleUser.php
index 6980ff9..fd416e8 100644
--- a/core/modules/user/src/Plugin/Action/AddRoleUser.php
+++ b/core/modules/user/src/Plugin/Action/AddRoleUser.php
@@ -8,7 +8,11 @@
  * @Action(
  *   id = "user_add_role_action",
  *   label = @Translation("Add a role to the selected users"),
- *   type = "user"
+ *   context = {
+ *     "user" = @ContextDefinition("entity:user",
+ *       label = @Translation("User")
+ *     )
+ *   }
  * )
  */
 class AddRoleUser extends ChangeUserRoleBase {
@@ -16,10 +20,11 @@ class AddRoleUser extends ChangeUserRoleBase {
   /**
    * {@inheritdoc}
    */
-  public function execute($account = NULL) {
+  public function execute() {
+    $account = $this->getContextValue('user');
     $rid = $this->configuration['rid'];
     // Skip adding the role to the user if they already have it.
-    if ($account !== FALSE && !$account->hasRole($rid)) {
+    if (!$account->hasRole($rid)) {
       // For efficiency manually save the original account before applying
       // any changes.
       $account->original = clone $account;
diff --git a/core/modules/user/src/Plugin/Action/BlockUser.php b/core/modules/user/src/Plugin/Action/BlockUser.php
index 6a875f7..5bd99bd 100644
--- a/core/modules/user/src/Plugin/Action/BlockUser.php
+++ b/core/modules/user/src/Plugin/Action/BlockUser.php
@@ -11,7 +11,11 @@
  * @Action(
  *   id = "user_block_user_action",
  *   label = @Translation("Block the selected users"),
- *   type = "user"
+ *   context = {
+ *     "user" = @ContextDefinition("entity:user",
+ *       label = @Translation("User")
+ *     )
+ *   }
  * )
  */
 class BlockUser extends ActionBase {
@@ -19,9 +23,10 @@ class BlockUser extends ActionBase {
   /**
    * {@inheritdoc}
    */
-  public function execute($account = NULL) {
+  public function execute() {
+    $account = $this->getContextValue('user');
     // Skip blocking user if they are already blocked.
-    if ($account !== FALSE && $account->isActive()) {
+    if ($account->isActive()) {
       // For efficiency manually save the original account before applying any
       // changes.
       $account->original = clone $account;
diff --git a/core/modules/user/src/Plugin/Action/CancelUser.php b/core/modules/user/src/Plugin/Action/CancelUser.php
index 140ef42..7ceacdf 100644
--- a/core/modules/user/src/Plugin/Action/CancelUser.php
+++ b/core/modules/user/src/Plugin/Action/CancelUser.php
@@ -14,7 +14,11 @@
  * @Action(
  *   id = "user_cancel_user_action",
  *   label = @Translation("Cancel the selected user accounts"),
- *   type = "user",
+ *   context = {
+ *     "user" = @ContextDefinition("entity:user",
+ *       label = @Translation("User")
+ *     )
+ *   },
  *   confirm_form_route_name = "user.multiple_cancel_confirm"
  * )
  */
diff --git a/core/modules/user/src/Plugin/Action/RemoveRoleUser.php b/core/modules/user/src/Plugin/Action/RemoveRoleUser.php
index 66cf6d0..7d5a424 100644
--- a/core/modules/user/src/Plugin/Action/RemoveRoleUser.php
+++ b/core/modules/user/src/Plugin/Action/RemoveRoleUser.php
@@ -8,7 +8,11 @@
  * @Action(
  *   id = "user_remove_role_action",
  *   label = @Translation("Remove a role from the selected users"),
- *   type = "user"
+ *   context = {
+ *     "user" = @ContextDefinition("entity:user",
+ *       label = @Translation("User")
+ *     )
+ *   }
  * )
  */
 class RemoveRoleUser extends ChangeUserRoleBase {
@@ -16,10 +20,11 @@ class RemoveRoleUser extends ChangeUserRoleBase {
   /**
    * {@inheritdoc}
    */
-  public function execute($account = NULL) {
+  public function execute() {
+    $account = $this->getContextValue('user');
     $rid = $this->configuration['rid'];
     // Skip removing the role from the user if they already don't have it.
-    if ($account !== FALSE && $account->hasRole($rid)) {
+    if ($account->hasRole($rid)) {
       // For efficiency manually save the original account before applying
       // any changes.
       $account->original = clone $account;
diff --git a/core/modules/user/src/Plugin/Action/UnblockUser.php b/core/modules/user/src/Plugin/Action/UnblockUser.php
index 68c59b4..bae4bb5 100644
--- a/core/modules/user/src/Plugin/Action/UnblockUser.php
+++ b/core/modules/user/src/Plugin/Action/UnblockUser.php
@@ -11,7 +11,11 @@
  * @Action(
  *   id = "user_unblock_user_action",
  *   label = @Translation("Unblock the selected users"),
- *   type = "user"
+ *   context = {
+ *     "user" = @ContextDefinition("entity:user",
+ *       label = @Translation("User")
+ *     )
+ *   }
  * )
  */
 class UnblockUser extends ActionBase {
@@ -19,9 +23,10 @@ class UnblockUser extends ActionBase {
   /**
    * {@inheritdoc}
    */
-  public function execute($account = NULL) {
+  public function execute() {
+    $account = $this->getContextValue('user');
     // Skip unblocking user if they are already unblocked.
-    if ($account !== FALSE && $account->isBlocked()) {
+    if ($account->isBlocked()) {
       $account->activate();
       $account->save();
     }
diff --git a/core/modules/user/tests/src/Unit/Plugin/Action/AddRoleUserTest.php b/core/modules/user/tests/src/Unit/Plugin/Action/AddRoleUserTest.php
index 5ca694b..31ca5bc 100644
--- a/core/modules/user/tests/src/Unit/Plugin/Action/AddRoleUserTest.php
+++ b/core/modules/user/tests/src/Unit/Plugin/Action/AddRoleUserTest.php
@@ -2,8 +2,6 @@
 
 namespace Drupal\Tests\user\Unit\Plugin\Action;
 
-use Drupal\user\Plugin\Action\AddRoleUser;
-
 /**
  * @coversDefaultClass \Drupal\user\Plugin\Action\AddRoleUser
  * @group user
@@ -23,9 +21,10 @@ public function testExecuteAddExistingRole() {
       ->will($this->returnValue(TRUE));
 
     $config = ['rid' => 'test_role_1'];
-    $remove_role_plugin = new AddRoleUser($config, 'user_add_role_action', ['type' => 'user'], $this->userRoleEntityType);
+    $add_role_plugin = $this->actionManager->createInstance('user_add_role_action', $config);
 
-    $remove_role_plugin->execute($this->account);
+    $add_role_plugin->setContextValue('user', $this->account);
+    $add_role_plugin->execute();
   }
 
   /**
@@ -41,9 +40,10 @@ public function testExecuteAddNonExistingRole() {
       ->will($this->returnValue(FALSE));
 
     $config = ['rid' => 'test_role_1'];
-    $remove_role_plugin = new AddRoleUser($config, 'user_remove_role_action', ['type' => 'user'], $this->userRoleEntityType);
+    $add_role_plugin = $this->actionManager->createInstance('user_add_role_action', $config);
 
-    $remove_role_plugin->execute($this->account);
+    $add_role_plugin->setContextValue('user', $this->account);
+    $add_role_plugin->execute();
   }
 
 }
diff --git a/core/modules/user/tests/src/Unit/Plugin/Action/RemoveRoleUserTest.php b/core/modules/user/tests/src/Unit/Plugin/Action/RemoveRoleUserTest.php
index 878b4ba..88c0a0a 100644
--- a/core/modules/user/tests/src/Unit/Plugin/Action/RemoveRoleUserTest.php
+++ b/core/modules/user/tests/src/Unit/Plugin/Action/RemoveRoleUserTest.php
@@ -2,8 +2,6 @@
 
 namespace Drupal\Tests\user\Unit\Plugin\Action;
 
-use Drupal\user\Plugin\Action\RemoveRoleUser;
-
 /**
  * @coversDefaultClass \Drupal\user\Plugin\Action\RemoveRoleUser
  * @group user
@@ -23,9 +21,10 @@ public function testExecuteRemoveExistingRole() {
       ->will($this->returnValue(TRUE));
 
     $config = ['rid' => 'test_role_1'];
-    $remove_role_plugin = new RemoveRoleUser($config, 'user_remove_role_action', ['type' => 'user'], $this->userRoleEntityType);
+    $remove_role_plugin = $this->actionManager->createInstance('user_remove_role_action', $config);
 
-    $remove_role_plugin->execute($this->account);
+    $remove_role_plugin->setContextValue('user', $this->account);
+    $remove_role_plugin->execute();
   }
 
   /**
@@ -41,8 +40,9 @@ public function testExecuteRemoveNonExistingRole() {
       ->will($this->returnValue(FALSE));
 
     $config = ['rid' => 'test_role_1'];
-    $remove_role_plugin = new RemoveRoleUser($config, 'user_remove_role_action', ['type' => 'user'], $this->userRoleEntityType);
+    $remove_role_plugin = $this->actionManager->createInstance('user_remove_role_action', $config);
 
+    $remove_role_plugin->setContextValue('user', $this->account);
     $remove_role_plugin->execute($this->account);
   }
 
diff --git a/core/modules/user/tests/src/Unit/Plugin/Action/RoleUserTestBase.php b/core/modules/user/tests/src/Unit/Plugin/Action/RoleUserTestBase.php
index a487d82..7475796 100644
--- a/core/modules/user/tests/src/Unit/Plugin/Action/RoleUserTestBase.php
+++ b/core/modules/user/tests/src/Unit/Plugin/Action/RoleUserTestBase.php
@@ -2,12 +2,12 @@
 
 namespace Drupal\Tests\user\Unit\Plugin\Action;
 
-use Drupal\Tests\UnitTestCase;
+use Drupal\Tests\Core\Action\ActionTestBase;
 
 /**
  * Provides a base class for user role action tests.
  */
-abstract class RoleUserTestBase extends UnitTestCase {
+abstract class RoleUserTestBase extends ActionTestBase {
 
   /**
    * The mocked account.
@@ -26,8 +26,9 @@
   /**
    * {@inheritdoc}
    */
-  protected function setUp() {
+  public function setUp() {
     parent::setUp();
+    $this->enableModule('user');
 
     $this->account = $this
       ->getMockBuilder('Drupal\user\Entity\User')
diff --git a/core/modules/user/tests/src/Unit/Plugin/views/field/UserBulkFormTest.php b/core/modules/user/tests/src/Unit/Plugin/views/field/UserBulkFormTest.php
index 11a970a..554d6c9 100644
--- a/core/modules/user/tests/src/Unit/Plugin/views/field/UserBulkFormTest.php
+++ b/core/modules/user/tests/src/Unit/Plugin/views/field/UserBulkFormTest.php
@@ -3,6 +3,8 @@
 namespace Drupal\Tests\user\Unit\Plugin\views\field;
 
 use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\Plugin\Context\ContextDefinition;
+use Drupal\Core\Plugin\Context\ContextHandler;
 use Drupal\Tests\UnitTestCase;
 use Drupal\user\Plugin\views\field\UserBulkForm;
 
@@ -25,26 +27,44 @@ protected function tearDown() {
    * Tests the constructor assignment of actions.
    */
   public function testConstructor() {
-    $actions = [];
+    $action_configs = [];
 
     for ($i = 1; $i <= 2; $i++) {
-      $action = $this->getMock('\Drupal\system\ActionConfigEntityInterface');
+      $action = $this->getMock('\Drupal\Core\Action\ActionInterface');
       $action->expects($this->any())
-        ->method('getType')
-        ->will($this->returnValue('user'));
-      $actions[$i] = $action;
+        ->method('getPluginDefinition')
+        ->will($this->returnValue([
+          'context' => [
+            'user' => new ContextDefinition('entity:user')
+          ]
+        ]));
+
+      $action_config = $this->getMock('\Drupal\system\ActionConfigEntityInterface');
+      $action_config->expects($this->any())
+        ->method('getPlugin')
+        ->will($this->returnValue($action));
+      $action_configs[$i] = $action_config;
     }
 
-    $action = $this->getMock('\Drupal\system\ActionConfigEntityInterface');
+    $action = $this->getMock('\Drupal\Core\Action\ActionInterface');
     $action->expects($this->any())
-      ->method('getType')
-      ->will($this->returnValue('node'));
-    $actions[] = $action;
+      ->method('getPluginDefinition')
+      ->will($this->returnValue([
+        'context' => [
+          'node' => new ContextDefinition('entity:node')
+        ]
+      ]));
+
+    $action_config = $this->getMock('\Drupal\system\ActionConfigEntityInterface');
+    $action_config->expects($this->any())
+      ->method('getPlugin')
+      ->will($this->returnValue($action));
+    $action_configs[] = $action_config;
 
     $entity_storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface');
     $entity_storage->expects($this->any())
       ->method('loadMultiple')
-      ->will($this->returnValue($actions));
+      ->will($this->returnValue($action_configs));
 
     $entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
     $entity_manager->expects($this->once())
@@ -84,10 +104,10 @@ public function testConstructor() {
     $definition['title'] = '';
     $options = [];
 
-    $user_bulk_form = new UserBulkForm([], 'user_bulk_form', $definition, $entity_manager, $language_manager);
+    $user_bulk_form = new UserBulkForm([], 'user_bulk_form', $definition, $entity_storage, $language_manager, new ContextHandler());
     $user_bulk_form->init($executable, $display, $options);
 
-    $this->assertAttributeEquals(array_slice($actions, 0, -1, TRUE), 'actions', $user_bulk_form);
+    $this->assertAttributeEquals(array_slice($action_configs, 0, -1, TRUE), 'actions', $user_bulk_form);
   }
 
 }
diff --git a/core/modules/views/views.views.inc b/core/modules/views/views.views.inc
index 0bd5f27..15c2316 100644
--- a/core/modules/views/views.views.inc
+++ b/core/modules/views/views.views.inc
@@ -144,21 +144,24 @@ function views_views_data() {
     }
   }
 
+  $context_handler = \Drupal::service('context.handler');
   // Registers an action bulk form per entity.
   foreach (\Drupal::entityManager()->getDefinitions() as $entity_type => $entity_info) {
-    $actions = array_filter(\Drupal::entityManager()->getStorage('action')->loadMultiple(), function (ActionConfigEntityInterface $action) use ($entity_type) {
-      return $action->getType() == $entity_type;
+    $match_context = ['context' => new Context(new ContextDefinition("entity:$entity_type"))];
+    // Filter the actions to only include those that match for this entity type.
+    $actions = array_filter(\Drupal::entityManager()->getStorage('action')->loadMultiple(), function ($action) use ($context_handler, $match_context) {
+      $filtered = $context_handler->filterPluginDefinitionsByContexts($match_context, [$action->getPlugin()->getPluginDefinition()]);
+      return !empty($filtered);
     });
-    if (empty($actions)) {
-      continue;
+    if (!empty($actions)) {
+      $data[$entity_info->getBaseTable()][$entity_type . '_bulk_form'] = [
+        'title' => t('Bulk update'),
+        'help' => t('Allows users to apply an action to one or more items.'),
+        'field' => [
+          'id' => 'bulk_form',
+        ],
+      ];
     }
-    $data[$entity_info->getBaseTable()][$entity_type . '_bulk_form'] = [
-      'title' => t('Bulk update'),
-      'help' => t('Allows users to apply an action to one or more items.'),
-      'field' => [
-        'id' => 'bulk_form',
-      ],
-    ];
   }
 
   // Registers views data for the entity itself.
