diff --git a/core/config/schema/core.entity.schema.yml b/core/config/schema/core.entity.schema.yml
index df2a2fd02d..5f1f84f78c 100644
--- a/core/config/schema/core.entity.schema.yml
+++ b/core/config/schema/core.entity.schema.yml
@@ -362,3 +362,15 @@ field.formatter.settings.entity_reference_label:
       type: boolean
       label: 'Link label to the referenced entity'
 
+# Schema for entity actions.
+action.configuration.entity_publish_action:*:
+  type: action_configuration_default
+  label: 'Publishing entity'
+
+action.configuration.entity_save_action:*:
+  type: action_configuration_default
+  label: 'Saving entity'
+
+action.configuration.entity_unpublish_action:*:
+  type: action_configuration_default
+  label: 'Unpublishing entity'
diff --git a/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityActionDeriverBase.php b/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityActionDeriverBase.php
new file mode 100644
index 0000000000..29159c2931
--- /dev/null
+++ b/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityActionDeriverBase.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace Drupal\Core\Action\Plugin\Action\Derivative;
+
+use Drupal\Component\Plugin\Derivative\DeriverBase;
+use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a base action for each entity type with specific interfaces.
+ */
+abstract class EntityActionDeriverBase extends DeriverBase implements ContainerDeriverInterface {
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Constructs a new EntityActionDeriverBase object.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, $base_plugin_id) {
+    return new static($container->get('entity_type.manager'));
+  }
+
+  /**
+   * Indicates whether the deriver can be used for the provided entity type.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type.
+   *
+   * @return bool
+   *   TRUE if the entity type can be used, FALSE otherwise.
+   */
+  abstract protected function isApplicable(EntityTypeInterface $entity_type);
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeDefinitions($base_plugin_definition) {
+    if (empty($this->derivatives)) {
+      $definitions = [];
+      foreach ($this->getApplicableEntityTypes() as $entity_type_id => $entity_type) {
+        $definition = $base_plugin_definition;
+        $definition['type'] = $entity_type_id;
+        $definition['label'] = sprintf('%s %s', $base_plugin_definition['action_label'], $entity_type->getSingularLabel());
+        $definitions[$entity_type_id] = $definition;
+      }
+      $this->derivatives = $definitions;
+    }
+
+    return parent::getDerivativeDefinitions($base_plugin_definition);
+  }
+
+  /**
+   * Gets a list of applicable entity types.
+   *
+   * The list consists of all entity types which match the conditions for the
+   * given deriver.
+   * For example, if the action applies to entities that are publishable,
+   * this method will find all entity types that are publishable.
+   *
+   * @return \Drupal\Core\Entity\EntityTypeInterface[]
+   *   The applicable entity types, keyed by entity type ID.
+   */
+  protected function getApplicableEntityTypes() {
+    $entity_types = $this->entityTypeManager->getDefinitions();
+    $entity_types = array_filter($entity_types, function (EntityTypeInterface $entity_type) {
+      return $this->isApplicable($entity_type);
+    });
+
+    return $entity_types;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityChangedActionDeriver.php b/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityChangedActionDeriver.php
new file mode 100644
index 0000000000..2df090b0d9
--- /dev/null
+++ b/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityChangedActionDeriver.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Drupal\Core\Action\Plugin\Action\Derivative;
+
+use Drupal\Core\Entity\EntityChangedInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+
+/**
+ * Provides an action deriver that finds entity types of EntityChangedInterface.
+ *
+ * @see \Drupal\Core\Action\Plugin\Action\SaveAction
+ */
+class EntityChangedActionDeriver extends EntityActionDeriverBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function isApplicable(EntityTypeInterface $entity_type) {
+    return $entity_type->entityClassImplements(EntityChangedInterface::class);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityPublishedActionDeriver.php b/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityPublishedActionDeriver.php
new file mode 100644
index 0000000000..05548eeba8
--- /dev/null
+++ b/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityPublishedActionDeriver.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Drupal\Core\Action\Plugin\Action\Derivative;
+
+use Drupal\Core\Entity\EntityPublishedInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+
+/**
+ * Provides an action deriver that finds publishable entity types.
+ *
+ * @see \Drupal\Core\Action\Plugin\Action\PublishAction
+ * @see \Drupal\Core\Action\Plugin\Action\UnpublishAction
+ */
+class EntityPublishedActionDeriver extends EntityActionDeriverBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function isApplicable(EntityTypeInterface $entity_type) {
+    return $entity_type->entityClassImplements(EntityPublishedInterface::class);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Action/Plugin/Action/EntityActionBase.php b/core/lib/Drupal/Core/Action/Plugin/Action/EntityActionBase.php
new file mode 100644
index 0000000000..2624024903
--- /dev/null
+++ b/core/lib/Drupal/Core/Action/Plugin/Action/EntityActionBase.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace Drupal\Core\Action\Plugin\Action;
+
+use Drupal\Component\Plugin\DependentPluginInterface;
+use Drupal\Core\Action\ActionBase;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Base class for entity-based actions.
+ */
+abstract class EntityActionBase extends ActionBase implements DependentPluginInterface, ContainerFactoryPluginInterface {
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Constructs a EntityActionBase object.
+   *
+   * @param mixed[] $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin ID for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('entity_type.manager')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function calculateDependencies() {
+    $module_name = $this->entityTypeManager
+      ->getDefinition($this->getPluginDefinition()['type'])
+      ->getProvider();
+    return ['module' => [$module_name]];
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Action/Plugin/Action/PublishAction.php b/core/lib/Drupal/Core/Action/Plugin/Action/PublishAction.php
new file mode 100644
index 0000000000..24432dcf1c
--- /dev/null
+++ b/core/lib/Drupal/Core/Action/Plugin/Action/PublishAction.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Drupal\Core\Action\Plugin\Action;
+
+use Drupal\Core\Session\AccountInterface;
+
+/**
+ * Publishes an entity.
+ *
+ * @Action(
+ *   id = "entity_publish_action",
+ *   action_label = @Translation("Publish"),
+ *   deriver = "Drupal\Core\Action\Plugin\Action\Derivative\EntityPublishedActionDeriver",
+ * )
+ */
+class PublishAction extends EntityActionBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute($entity = NULL) {
+    $entity->setPublished()->save();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+    $key = $object->getEntityType()->getKey('published');
+
+    /** @var \Drupal\Core\Entity\EntityInterface $object */
+    $result = $object->access('update', $account, TRUE)
+      ->andIf($object->$key->access('edit', $account, TRUE));
+
+    return $return_as_object ? $result : $result->isAllowed();
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Action/Plugin/Action/SaveAction.php b/core/lib/Drupal/Core/Action/Plugin/Action/SaveAction.php
new file mode 100644
index 0000000000..98f83410ed
--- /dev/null
+++ b/core/lib/Drupal/Core/Action/Plugin/Action/SaveAction.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Drupal\Core\Action\Plugin\Action;
+
+use Drupal\Component\Datetime\TimeInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Session\AccountInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides an action that can save any entity.
+ *
+ * @Action(
+ *   id = "entity_save_action",
+ *   action_label = @Translation("Save"),
+ *   deriver = "Drupal\Core\Action\Plugin\Action\Derivative\EntityChangedActionDeriver",
+ * )
+ */
+class SaveAction extends EntityActionBase {
+
+  /**
+   * The time service.
+   *
+   * @var \Drupal\Component\Datetime\TimeInterface
+   */
+  protected $time;
+
+  /**
+   * Constructs a SaveAction object.
+   *
+   * @param mixed[] $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin ID for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\Component\Datetime\TimeInterface $time
+   *   The time service.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, TimeInterface $time) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager);
+    $this->time = $time;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('entity_type.manager'),
+      $container->get('datetime.time')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute($entity = NULL) {
+    $entity->setChangedTime($this->time->getRequestTime())->save();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+    /** @var \Drupal\Core\Entity\EntityInterface $object */
+    return $object->access('update', $account, $return_as_object);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Action/Plugin/Action/UnpublishAction.php b/core/lib/Drupal/Core/Action/Plugin/Action/UnpublishAction.php
new file mode 100644
index 0000000000..b146914cc2
--- /dev/null
+++ b/core/lib/Drupal/Core/Action/Plugin/Action/UnpublishAction.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Drupal\Core\Action\Plugin\Action;
+
+use Drupal\Core\Session\AccountInterface;
+
+/**
+ * Unpublishes an entity.
+ *
+ * @Action(
+ *   id = "entity_unpublish_action",
+ *   action_label = @Translation("Unpublish"),
+ *   deriver = "Drupal\Core\Action\Plugin\Action\Derivative\EntityPublishedActionDeriver",
+ * )
+ */
+class UnpublishAction extends EntityActionBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function execute($entity = NULL) {
+    $entity->setUnpublished()->save();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+    $key = $object->getEntityType()->getKey('published');
+
+    /** @var \Drupal\Core\Entity\EntityInterface $object */
+    $result = $object->access('update', $account, TRUE)
+      ->andIf($object->$key->access('edit', $account, TRUE));
+
+    return $return_as_object ? $result : $result->isAllowed();
+  }
+
+}
diff --git a/core/modules/action/migration_templates/d6_action.yml b/core/modules/action/migration_templates/d6_action.yml
index 592ed1805d..f7b9f74747 100644
--- a/core/modules/action/migration_templates/d6_action.yml
+++ b/core/modules/action/migration_templates/d6_action.yml
@@ -23,6 +23,12 @@ process:
         imagecache_flush_action: 0
         imagecache_generate_all_action: 0
         imagecache_generate_action: 0
+        comment_publish_action: entity_publish_action:comment
+        comment_unpublish_action: entity_unpublish_action:comment
+        comment_save_action: entity_save_action:comment
+        node_publish_action: entity_publish_action:node
+        node_unpublish_action: entity_unpublish_action:node
+        node_save_action: entity_save_action:node
       bypass: true
     -
       plugin: skip_on_empty
diff --git a/core/modules/action/migration_templates/d7_action.yml b/core/modules/action/migration_templates/d7_action.yml
index 03b869af18..da9ffe10fd 100644
--- a/core/modules/action/migration_templates/d7_action.yml
+++ b/core/modules/action/migration_templates/d7_action.yml
@@ -20,6 +20,12 @@ process:
         system_send_email_action: action_send_email_action
         system_message_action: action_message_action
         system_block_ip_action: 0
+        comment_publish_action: entity_publish_action:comment
+        comment_unpublish_action: entity_unpublish_action:comment
+        comment_save_action: entity_save_action:comment
+        node_publish_action: entity_publish_action:node
+        node_unpublish_action: entity_unpublish_action:node
+        node_save_action: entity_save_action:node
       bypass: true
     -
       plugin: skip_on_empty
diff --git a/core/modules/comment/config/install/system.action.comment_publish_action.yml b/core/modules/comment/config/install/system.action.comment_publish_action.yml
index 8fbd48d349..8befe79f0c 100644
--- a/core/modules/comment/config/install/system.action.comment_publish_action.yml
+++ b/core/modules/comment/config/install/system.action.comment_publish_action.yml
@@ -6,5 +6,5 @@ dependencies:
 id: comment_publish_action
 label: 'Publish comment'
 type: comment
-plugin: comment_publish_action
+plugin: entity_publish_action:comment
 configuration: {  }
diff --git a/core/modules/comment/config/install/system.action.comment_save_action.yml b/core/modules/comment/config/install/system.action.comment_save_action.yml
index 640d2811cd..48fdfc9575 100644
--- a/core/modules/comment/config/install/system.action.comment_save_action.yml
+++ b/core/modules/comment/config/install/system.action.comment_save_action.yml
@@ -6,5 +6,5 @@ dependencies:
 id: comment_save_action
 label: 'Save comment'
 type: comment
-plugin: comment_save_action
+plugin: entity_save_action:comment
 configuration: {  }
diff --git a/core/modules/comment/config/install/system.action.comment_unpublish_action.yml b/core/modules/comment/config/install/system.action.comment_unpublish_action.yml
index 99902e8be3..5618409ffb 100644
--- a/core/modules/comment/config/install/system.action.comment_unpublish_action.yml
+++ b/core/modules/comment/config/install/system.action.comment_unpublish_action.yml
@@ -6,5 +6,5 @@ dependencies:
 id: comment_unpublish_action
 label: 'Unpublish comment'
 type: comment
-plugin: comment_unpublish_action
+plugin: entity_unpublish_action:comment
 configuration: {  }
diff --git a/core/modules/comment/config/schema/comment.schema.yml b/core/modules/comment/config/schema/comment.schema.yml
index 045e9ad544..ba5874ab86 100644
--- a/core/modules/comment/config/schema/comment.schema.yml
+++ b/core/modules/comment/config/schema/comment.schema.yml
@@ -15,14 +15,6 @@ field.widget.settings.comment_default:
   type: mapping
   label: 'Comment display format settings'
 
-action.configuration.comment_publish_action:
-  type: action_configuration_default
-  label: 'Publish comment configuration'
-
-action.configuration.comment_save_action:
-  type: action_configuration_default
-  label: 'Save comment configuration'
-
 action.configuration.comment_unpublish_by_keyword_action:
   type: mapping
   label: 'Unpublish comment containing keyword(s) configuration'
@@ -34,10 +26,6 @@ action.configuration.comment_unpublish_by_keyword_action:
         type: string
         label: 'Keyword'
 
-action.configuration.comment_unpublish_action:
-  type: action_configuration_default
-  label: 'Unpublish comment configuration'
-
 action.configuration.comment_delete_action:
   type: action_configuration_default
   label: 'Delete comment configuration'
diff --git a/core/modules/comment/src/Plugin/Action/PublishComment.php b/core/modules/comment/src/Plugin/Action/PublishComment.php
index 14f5605c74..f481f3e6ac 100644
--- a/core/modules/comment/src/Plugin/Action/PublishComment.php
+++ b/core/modules/comment/src/Plugin/Action/PublishComment.php
@@ -2,37 +2,23 @@
 
 namespace Drupal\comment\Plugin\Action;
 
-use Drupal\Core\Action\ActionBase;
-use Drupal\Core\Session\AccountInterface;
+@trigger_error(__NAMESPACE__ . '\PublishComment is deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0. Use \Drupal\Core\Action\Plugin\Action\PublishAction instead. See https://www.drupal.org/node/2919303.', E_USER_DEPRECATED);
+
+use Drupal\Core\Action\Plugin\Action\PublishAction;
 
 /**
  * Publishes a comment.
  *
+ * @deprecated in Drupal 8.5.x, to be removed before Drupal 9.0.0.
+ *   Use \Drupal\Core\Action\Plugin\Action\PublishAction instead.
+ *
+ * @see \Drupal\Core\Action\Plugin\Action\PublishAction
+ * @see https://www.drupal.org/node/2919303
+ *
  * @Action(
  *   id = "comment_publish_action",
  *   label = @Translation("Publish comment"),
  *   type = "comment"
  * )
  */
-class PublishComment extends ActionBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function execute($comment = NULL) {
-    $comment->setPublished(TRUE);
-    $comment->save();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
-    /** @var \Drupal\comment\CommentInterface $object */
-    $result = $object->status->access('edit', $account, TRUE)
-      ->andIf($object->access('update', $account, TRUE));
-
-    return $return_as_object ? $result : $result->isAllowed();
-  }
-
-}
+class PublishComment extends PublishAction {}
diff --git a/core/modules/comment/src/Plugin/Action/SaveComment.php b/core/modules/comment/src/Plugin/Action/SaveComment.php
index fdab65d234..656774fd49 100644
--- a/core/modules/comment/src/Plugin/Action/SaveComment.php
+++ b/core/modules/comment/src/Plugin/Action/SaveComment.php
@@ -2,33 +2,23 @@
 
 namespace Drupal\comment\Plugin\Action;
 
-use Drupal\Core\Action\ActionBase;
-use Drupal\Core\Session\AccountInterface;
+@trigger_error(__NAMESPACE__ . '\SaveComment is deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0. Use \Drupal\Core\Action\Plugin\Action\SaveAction instead. See https://www.drupal.org/node/2919303.', E_USER_DEPRECATED);
+
+use Drupal\Core\Action\Plugin\Action\SaveAction;
 
 /**
  * Saves a comment.
  *
+ * @deprecated in Drupal 8.5.x, to be removed before Drupal 9.0.0.
+ *   Use \Drupal\Core\Action\Plugin\Action\SaveAction instead.
+ *
+ * @see \Drupal\Core\Action\Plugin\Action\SaveAction
+ * @see https://www.drupal.org/node/2919303
+ *
  * @Action(
  *   id = "comment_save_action",
  *   label = @Translation("Save comment"),
  *   type = "comment"
  * )
  */
-class SaveComment extends ActionBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function execute($comment = NULL) {
-    $comment->save();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
-    /** @var \Drupal\comment\CommentInterface $object */
-    return $object->access('update', $account, $return_as_object);
-  }
-
-}
+class SaveComment extends SaveAction {}
diff --git a/core/modules/comment/src/Plugin/Action/UnpublishComment.php b/core/modules/comment/src/Plugin/Action/UnpublishComment.php
index 6accccc834..d7019e4c70 100644
--- a/core/modules/comment/src/Plugin/Action/UnpublishComment.php
+++ b/core/modules/comment/src/Plugin/Action/UnpublishComment.php
@@ -2,37 +2,23 @@
 
 namespace Drupal\comment\Plugin\Action;
 
-use Drupal\Core\Action\ActionBase;
-use Drupal\Core\Session\AccountInterface;
+@trigger_error(__NAMESPACE__ . '\UnpublishComment is deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0. Use \Drupal\Core\Action\Plugin\Action\UnpublishAction instead. See https://www.drupal.org/node/2919303.', E_USER_DEPRECATED);
+
+use Drupal\Core\Action\Plugin\Action\UnpublishAction;
 
 /**
  * Unpublishes a comment.
  *
+ * @deprecated in Drupal 8.5.x, to be removed before Drupal 9.0.0.
+ *   Use \Drupal\Core\Action\Plugin\Action\UnpublishAction instead.
+ *
+ * @see \Drupal\Core\Action\Plugin\Action\UnpublishAction
+ * @see https://www.drupal.org/node/2919303
+ *
  * @Action(
  *   id = "comment_unpublish_action",
  *   label = @Translation("Unpublish comment"),
  *   type = "comment"
  * )
  */
-class UnpublishComment extends ActionBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function execute($comment = NULL) {
-    $comment->setPublished(FALSE);
-    $comment->save();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
-    /** @var \Drupal\comment\CommentInterface $object */
-    $result = $object->status->access('edit', $account, TRUE)
-      ->andIf($object->access('update', $account, TRUE));
-
-    return $return_as_object ? $result : $result->isAllowed();
-  }
-
-}
+class UnpublishComment extends UnpublishAction {}
diff --git a/core/modules/comment/tests/src/Functional/CommentActionsTest.php b/core/modules/comment/tests/src/Functional/CommentActionsTest.php
index 79786fcc05..326749b7c0 100644
--- a/core/modules/comment/tests/src/Functional/CommentActionsTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentActionsTest.php
@@ -32,7 +32,7 @@ public function testCommentPublishUnpublishActions() {
     $action = Action::load('comment_unpublish_action');
     $action->execute([$comment]);
     $this->assertTrue($comment->isPublished() === FALSE, 'Comment was unpublished');
-
+    $this->assertArraySubset(['module' => ['comment']], $action->getDependencies());
     // Publish a comment.
     $action = Action::load('comment_publish_action');
     $action->execute([$comment]);
diff --git a/core/modules/content_moderation/content_moderation.module b/core/modules/content_moderation/content_moderation.module
index 5a7af0e2c9..2cf1fab09a 100644
--- a/core/modules/content_moderation/content_moderation.module
+++ b/core/modules/content_moderation/content_moderation.module
@@ -8,8 +8,8 @@
 use Drupal\content_moderation\EntityOperations;
 use Drupal\content_moderation\EntityTypeInfo;
 use Drupal\content_moderation\ContentPreprocess;
-use Drupal\content_moderation\Plugin\Action\ModerationOptOutPublishNode;
-use Drupal\content_moderation\Plugin\Action\ModerationOptOutUnpublishNode;
+use Drupal\content_moderation\Plugin\Action\ModerationOptOutPublish;
+use Drupal\content_moderation\Plugin\Action\ModerationOptOutUnpublish;
 use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
 use Drupal\Core\Entity\EntityInterface;
@@ -21,8 +21,8 @@
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\workflows\WorkflowInterface;
-use Drupal\node\Plugin\Action\PublishNode;
-use Drupal\node\Plugin\Action\UnpublishNode;
+use Drupal\Core\Action\Plugin\Action\PublishAction;
+use Drupal\Core\Action\Plugin\Action\UnpublishAction;
 use Drupal\workflows\Entity\Workflow;
 
 /**
@@ -227,13 +227,15 @@ function content_moderation_action_info_alter(&$definitions) {
 
   // The publish/unpublish actions are not valid on moderated entities. So swap
   // their implementations out for alternates that will become a no-op on a
-  // moderated node. If another module has already swapped out those classes,
+  // moderated entity. If another module has already swapped out those classes,
   // though, we'll be polite and do nothing.
-  if (isset($definitions['node_publish_action']['class']) && $definitions['node_publish_action']['class'] == PublishNode::class) {
-    $definitions['node_publish_action']['class'] = ModerationOptOutPublishNode::class;
-  }
-  if (isset($definitions['node_unpublish_action']['class']) && $definitions['node_unpublish_action']['class'] == UnpublishNode::class) {
-    $definitions['node_unpublish_action']['class'] = ModerationOptOutUnpublishNode::class;
+  foreach ($definitions as &$definition) {
+    if ($definition['id'] === 'entity_publish_action' && $definition['class'] == PublishAction::class) {
+      $definition['class'] = ModerationOptOutPublish::class;
+    }
+    if ($definition['id'] === 'entity_unpublish_action' && $definition['class'] == UnpublishAction::class) {
+      $definition['class'] = ModerationOptOutUnpublish::class;
+    }
   }
 }
 
diff --git a/core/modules/content_moderation/src/Plugin/Action/ModerationOptOutPublish.php b/core/modules/content_moderation/src/Plugin/Action/ModerationOptOutPublish.php
new file mode 100644
index 0000000000..6a707e852f
--- /dev/null
+++ b/core/modules/content_moderation/src/Plugin/Action/ModerationOptOutPublish.php
@@ -0,0 +1,84 @@
+<?php
+
+namespace Drupal\content_moderation\Plugin\Action;
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Action\Plugin\Action\PublishAction;
+use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\content_moderation\ModerationInformationInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Alternate action plugin that can opt-out of modifying moderated entities.
+ *
+ * @see \Drupal\Core\Action\Plugin\Action\PublishAction
+ */
+class ModerationOptOutPublish extends PublishAction implements ContainerFactoryPluginInterface {
+
+  /**
+   * Moderation information service.
+   *
+   * @var \Drupal\content_moderation\ModerationInformationInterface
+   */
+  protected $moderationInfo;
+
+  /**
+   * Bundle info service.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
+   */
+  protected $bundleInfo;
+
+  /**
+   * ModerationOptOutPublish constructor.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\content_moderation\ModerationInformationInterface $moderation_info
+   *   The moderation information service.
+   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info
+   *   Bundle info service.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, ModerationInformationInterface $moderation_info, EntityTypeBundleInfoInterface $bundle_info) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager);
+    $this->moderationInfo = $moderation_info;
+    $this->bundleInfo = $bundle_info;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration, $plugin_id, $plugin_definition,
+      $container->get('entity_type.manager'),
+      $container->get('content_moderation.moderation_information'),
+      $container->get('entity_type.bundle.info')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access($entity, AccountInterface $account = NULL, $return_as_object = FALSE) {
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+    if ($entity && $this->moderationInfo->isModeratedEntity($entity)) {
+      $bundle_info = $this->bundleInfo->getBundleInfo($entity->getEntityTypeId());
+      $bundle_label = $bundle_info[$entity->bundle()]['label'];
+      drupal_set_message($this->t("@bundle @label were skipped as they are under moderation and may not be directly published.", ['@bundle' => $bundle_label, '@label' => $entity->getEntityType()->getPluralLabel()]), 'warning');
+      $result = AccessResult::forbidden();
+      return $return_as_object ? $result : $result->isAllowed();
+    }
+    return parent::access($entity, $account, $return_as_object);
+  }
+
+}
diff --git a/core/modules/content_moderation/src/Plugin/Action/ModerationOptOutPublishNode.php b/core/modules/content_moderation/src/Plugin/Action/ModerationOptOutPublishNode.php
index b2d85335aa..706885dd7e 100644
--- a/core/modules/content_moderation/src/Plugin/Action/ModerationOptOutPublishNode.php
+++ b/core/modules/content_moderation/src/Plugin/Action/ModerationOptOutPublishNode.php
@@ -2,65 +2,16 @@
 
 namespace Drupal\content_moderation\Plugin\Action;
 
-use Drupal\Core\Access\AccessResult;
-use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
-use Drupal\Core\Session\AccountInterface;
-use Drupal\node\Plugin\Action\PublishNode;
-use Drupal\content_moderation\ModerationInformationInterface;
-use Symfony\Component\DependencyInjection\ContainerInterface;
+@trigger_error(__NAMESPACE__ . '\ModerationOptOutPublishNode is deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0. Use \Drupal\content_moderation\Plugin\Action\ModerationOptOutPublish instead. See https://www.drupal.org/node/2919303.', E_USER_DEPRECATED);
 
 /**
  * Alternate action plugin that can opt-out of modifying moderated entities.
  *
- * @see \Drupal\node\Plugin\Action\PublishNode
+ * @deprecated in Drupal 8.5.x, to be removed before Drupal 9.0.0.
+ *   Use \Drupal\content_moderation\Plugin\Action\ModerationOptOutPublish
+ *   instead.
+ *
+ * @see \Drupal\content_moderation\Plugin\Action\ModerationOptOutPublish
+ * @see https://www.drupal.org/node/2919303
  */
-class ModerationOptOutPublishNode extends PublishNode implements ContainerFactoryPluginInterface {
-
-  /**
-   * Moderation information service.
-   *
-   * @var \Drupal\content_moderation\ModerationInformationInterface
-   */
-  protected $moderationInfo;
-
-  /**
-   * ModerationOptOutPublishNode constructor.
-   *
-   * @param array $configuration
-   *   A configuration array containing information about the plugin instance.
-   * @param string $plugin_id
-   *   The plugin_id for the plugin instance.
-   * @param mixed $plugin_definition
-   *   The plugin implementation definition.
-   * @param \Drupal\content_moderation\ModerationInformationInterface $moderation_info
-   *   The moderation information service.
-   */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, ModerationInformationInterface $moderation_info) {
-    parent::__construct($configuration, $plugin_id, $plugin_definition);
-    $this->moderationInfo = $moderation_info;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
-    return new static(
-      $configuration, $plugin_id, $plugin_definition,
-      $container->get('content_moderation.moderation_information')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function access($entity, AccountInterface $account = NULL, $return_as_object = FALSE) {
-    /** @var \Drupal\node\NodeInterface $entity */
-    if ($entity && $this->moderationInfo->isModeratedEntity($entity)) {
-      drupal_set_message($this->t("@bundle @label were skipped as they are under moderation and may not be directly published.", ['@bundle' => node_get_type_label($entity), '@label' => $entity->getEntityType()->getPluralLabel()]), 'warning');
-      $result = AccessResult::forbidden();
-      return $return_as_object ? $result : $result->isAllowed();
-    }
-    return parent::access($entity, $account, $return_as_object);
-  }
-
-}
+class ModerationOptOutPublishNode extends ModerationOptOutPublish {}
diff --git a/core/modules/content_moderation/src/Plugin/Action/ModerationOptOutUnpublish.php b/core/modules/content_moderation/src/Plugin/Action/ModerationOptOutUnpublish.php
new file mode 100644
index 0000000000..5a46b3e341
--- /dev/null
+++ b/core/modules/content_moderation/src/Plugin/Action/ModerationOptOutUnpublish.php
@@ -0,0 +1,84 @@
+<?php
+
+namespace Drupal\content_moderation\Plugin\Action;
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Action\Plugin\Action\UnpublishAction;
+use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\content_moderation\ModerationInformationInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Alternate action plugin that can opt-out of modifying moderated entities.
+ *
+ * @see \Drupal\Core\Action\Plugin\Action\UnpublishAction
+ */
+class ModerationOptOutUnpublish extends UnpublishAction implements ContainerFactoryPluginInterface {
+
+  /**
+   * Moderation information service.
+   *
+   * @var \Drupal\content_moderation\ModerationInformationInterface
+   */
+  protected $moderationInfo;
+
+  /**
+   * Bundle info service.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
+   */
+  protected $bundleInfo;
+
+  /**
+   * ModerationOptOutUnpublish constructor.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\content_moderation\ModerationInformationInterface $moderation_info
+   *   The moderation information service.
+   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info
+   *   Bundle info service.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, ModerationInformationInterface $moderation_info, EntityTypeBundleInfoInterface $bundle_info) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager);
+    $this->moderationInfo = $moderation_info;
+    $this->bundleInfo = $bundle_info;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration, $plugin_id, $plugin_definition,
+      $container->get('entity_type.manager'),
+      $container->get('content_moderation.moderation_information'),
+      $container->get('entity_type.bundle.info')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access($entity, AccountInterface $account = NULL, $return_as_object = FALSE) {
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+    if ($entity && $this->moderationInfo->isModeratedEntity($entity)) {
+      $bundle_info = $this->bundleInfo->getBundleInfo($entity->getEntityTypeId());
+      $bundle_label = $bundle_info[$entity->bundle()]['label'];
+      drupal_set_message($this->t("@bundle @label were skipped as they are under moderation and may not be directly unpublished.", ['@bundle' => $bundle_label, '@label' => $entity->getEntityType()->getPluralLabel()]), 'warning');
+      $result = AccessResult::forbidden();
+      return $return_as_object ? $result : $result->isAllowed();
+    }
+    return parent::access($entity, $account, $return_as_object);
+  }
+
+}
diff --git a/core/modules/content_moderation/src/Plugin/Action/ModerationOptOutUnpublishNode.php b/core/modules/content_moderation/src/Plugin/Action/ModerationOptOutUnpublishNode.php
index ee36169468..f482aa5cf2 100644
--- a/core/modules/content_moderation/src/Plugin/Action/ModerationOptOutUnpublishNode.php
+++ b/core/modules/content_moderation/src/Plugin/Action/ModerationOptOutUnpublishNode.php
@@ -2,65 +2,16 @@
 
 namespace Drupal\content_moderation\Plugin\Action;
 
-use Drupal\Core\Access\AccessResult;
-use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
-use Drupal\Core\Session\AccountInterface;
-use Drupal\node\Plugin\Action\UnpublishNode;
-use Drupal\content_moderation\ModerationInformationInterface;
-use Symfony\Component\DependencyInjection\ContainerInterface;
+@trigger_error(__NAMESPACE__ . '\ModerationOptOutUnpublishNode is deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0. Use \Drupal\content_moderation\Plugin\Action\ModerationOptOutUnpublish instead. See https://www.drupal.org/node/2919303.', E_USER_DEPRECATED);
 
 /**
  * Alternate action plugin that can opt-out of modifying moderated entities.
  *
- * @see \Drupal\node\Plugin\Action\UnpublishNode
+ * @deprecated in Drupal 8.5.x, to be removed before Drupal 9.0.0.
+ *   Use \Drupal\content_moderation\Plugin\Action\ModerationOptOutUnpublish
+ *   instead.
+ *
+ * @see \Drupal\content_moderation\Plugin\Action\ModerationOptOutPublish
+ * @see https://www.drupal.org/node/2919303
  */
-class ModerationOptOutUnpublishNode extends UnpublishNode implements ContainerFactoryPluginInterface {
-
-  /**
-   * Moderation information service.
-   *
-   * @var \Drupal\content_moderation\ModerationInformationInterface
-   */
-  protected $moderationInfo;
-
-  /**
-   * ModerationOptOutUnpublishNode constructor.
-   *
-   * @param array $configuration
-   *   A configuration array containing information about the plugin instance.
-   * @param string $plugin_id
-   *   The plugin_id for the plugin instance.
-   * @param mixed $plugin_definition
-   *   The plugin implementation definition.
-   * @param \Drupal\content_moderation\ModerationInformationInterface $moderation_info
-   *   The moderation information service.
-   */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, ModerationInformationInterface $moderation_info) {
-    parent::__construct($configuration, $plugin_id, $plugin_definition);
-    $this->moderationInfo = $moderation_info;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
-    return new static(
-      $configuration, $plugin_id, $plugin_definition,
-      $container->get('content_moderation.moderation_information')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function access($entity, AccountInterface $account = NULL, $return_as_object = FALSE) {
-    /** @var \Drupal\node\NodeInterface $entity */
-    if ($entity && $this->moderationInfo->isModeratedEntity($entity)) {
-      drupal_set_message($this->t("@bundle @label were skipped as they are under moderation and may not be directly unpublished.", ['@bundle' => node_get_type_label($entity), '@label' => $entity->getEntityType()->getPluralLabel()]), 'warning');
-      $result = AccessResult::forbidden();
-      return $return_as_object ? $result : $result->isAllowed();
-    }
-    return parent::access($entity, $account, $return_as_object);
-  }
-
-}
+class ModerationOptOutUnpublishNode extends ModerationOptOutUnpublish {}
diff --git a/core/modules/node/config/install/system.action.node_publish_action.yml b/core/modules/node/config/install/system.action.node_publish_action.yml
index 20ed19b639..85c775d48d 100644
--- a/core/modules/node/config/install/system.action.node_publish_action.yml
+++ b/core/modules/node/config/install/system.action.node_publish_action.yml
@@ -6,5 +6,5 @@ dependencies:
 id: node_publish_action
 label: 'Publish content'
 type: node
-plugin: node_publish_action
+plugin: entity_publish_action:node
 configuration: {  }
diff --git a/core/modules/node/config/install/system.action.node_save_action.yml b/core/modules/node/config/install/system.action.node_save_action.yml
index 887ff43d05..5991541b0b 100644
--- a/core/modules/node/config/install/system.action.node_save_action.yml
+++ b/core/modules/node/config/install/system.action.node_save_action.yml
@@ -6,5 +6,5 @@ dependencies:
 id: node_save_action
 label: 'Save content'
 type: node
-plugin: node_save_action
+plugin: entity_save_action:node
 configuration: {  }
diff --git a/core/modules/node/config/install/system.action.node_unpublish_action.yml b/core/modules/node/config/install/system.action.node_unpublish_action.yml
index 1e778c0dca..1f18e32c81 100644
--- a/core/modules/node/config/install/system.action.node_unpublish_action.yml
+++ b/core/modules/node/config/install/system.action.node_unpublish_action.yml
@@ -6,5 +6,5 @@ dependencies:
 id: node_unpublish_action
 label: 'Unpublish content'
 type: node
-plugin: node_unpublish_action
+plugin: entity_unpublish_action:node
 configuration: {  }
diff --git a/core/modules/node/config/schema/node.schema.yml b/core/modules/node/config/schema/node.schema.yml
index 11a93c5310..984b9aaab9 100644
--- a/core/modules/node/config/schema/node.schema.yml
+++ b/core/modules/node/config/schema/node.schema.yml
@@ -62,18 +62,6 @@ action.configuration.node_promote_action:
   type: action_configuration_default
   label: 'Promote selected content from front page configuration'
 
-action.configuration.node_publish_action:
-  type: action_configuration_default
-  label: 'Publish selected content configuration'
-
-action.configuration.node_unpublish_action:
-  type: action_configuration_default
-  label: 'Unpublish selected content configuration'
-
-action.configuration.node_save_action:
-  type: action_configuration_default
-  label: 'Save content configuration'
-
 action.configuration.node_delete_action:
   type: action_configuration_default
   label: 'Delete content configuration'
diff --git a/core/modules/node/src/Plugin/Action/PublishNode.php b/core/modules/node/src/Plugin/Action/PublishNode.php
index 2fa8c019ff..28928e94b3 100644
--- a/core/modules/node/src/Plugin/Action/PublishNode.php
+++ b/core/modules/node/src/Plugin/Action/PublishNode.php
@@ -2,25 +2,23 @@
 
 namespace Drupal\node\Plugin\Action;
 
-use Drupal\Core\Field\FieldUpdateActionBase;
-use Drupal\node\NodeInterface;
+@trigger_error(__NAMESPACE__ . '\PublishNode is deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0. Use \Drupal\Core\Action\Plugin\Action\PublishAction instead. See https://www.drupal.org/node/2919303.', E_USER_DEPRECATED);
+
+use Drupal\Core\Action\Plugin\Action\PublishAction;
 
 /**
  * Publishes a node.
  *
+ * @deprecated in Drupal 8.5.x, to be removed before Drupal 9.0.0.
+ *   Use \Drupal\Core\Action\Plugin\Action\PublishAction instead.
+ *
+ * @see \Drupal\Core\Action\Plugin\Action\PublishAction
+ * @see https://www.drupal.org/node/2919303
+ *
  * @Action(
  *   id = "node_publish_action",
  *   label = @Translation("Publish selected content"),
  *   type = "node"
  * )
  */
-class PublishNode extends FieldUpdateActionBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function getFieldsToUpdate() {
-    return ['status' => NodeInterface::PUBLISHED];
-  }
-
-}
+class PublishNode extends PublishAction {}
diff --git a/core/modules/node/src/Plugin/Action/SaveNode.php b/core/modules/node/src/Plugin/Action/SaveNode.php
index e358fef36c..04d4234bad 100644
--- a/core/modules/node/src/Plugin/Action/SaveNode.php
+++ b/core/modules/node/src/Plugin/Action/SaveNode.php
@@ -2,36 +2,23 @@
 
 namespace Drupal\node\Plugin\Action;
 
-use Drupal\Core\Action\ActionBase;
-use Drupal\Core\Session\AccountInterface;
+@trigger_error(__NAMESPACE__ . '\SaveNode is deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0. Use \Drupal\Core\Action\Plugin\Action\SaveAction instead. See https://www.drupal.org/node/2919303.', E_USER_DEPRECATED);
+
+use Drupal\Core\Action\Plugin\Action\SaveAction;
 
 /**
  * Provides an action that can save any entity.
  *
+ * @deprecated in Drupal 8.5.x, to be removed before Drupal 9.0.0.
+ *   Use \Drupal\Core\Action\Plugin\Action\SaveAction instead.
+ *
+ * @see \Drupal\Core\Action\Plugin\Action\SaveAction
+ * @see https://www.drupal.org/node/2919303
+ *
  * @Action(
  *   id = "node_save_action",
  *   label = @Translation("Save content"),
  *   type = "node"
  * )
  */
-class SaveNode extends ActionBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function execute($entity = NULL) {
-    // We need to change at least one value, otherwise the changed timestamp
-    // will not be updated.
-    $entity->changed = 0;
-    $entity->save();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
-    /** @var \Drupal\node\NodeInterface $object */
-    return $object->access('update', $account, $return_as_object);
-  }
-
-}
+class SaveNode extends SaveAction {}
diff --git a/core/modules/node/src/Plugin/Action/UnpublishNode.php b/core/modules/node/src/Plugin/Action/UnpublishNode.php
index 7c286e2fc4..1a17a577ad 100644
--- a/core/modules/node/src/Plugin/Action/UnpublishNode.php
+++ b/core/modules/node/src/Plugin/Action/UnpublishNode.php
@@ -2,25 +2,23 @@
 
 namespace Drupal\node\Plugin\Action;
 
-use Drupal\Core\Field\FieldUpdateActionBase;
-use Drupal\node\NodeInterface;
+@trigger_error(__NAMESPACE__ . '\UnpublishNode is deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0. Use \Drupal\Core\Action\Plugin\Action\UnpublishAction instead. See https://www.drupal.org/node/2919303.', E_USER_DEPRECATED);
+
+use Drupal\Core\Action\Plugin\Action\UnpublishAction;
 
 /**
  * Unpublishes a node.
  *
+ * @deprecated in Drupal 8.5.x, to be removed before Drupal 9.0.0.
+ *   Use \Drupal\Core\Action\Plugin\Action\UnpublishAction instead.
+ *
+ * @see \Drupal\Core\Action\Plugin\Action\UnpublishAction
+ * @see https://www.drupal.org/node/2919303
+ *
  * @Action(
  *   id = "node_unpublish_action",
  *   label = @Translation("Unpublish selected content"),
  *   type = "node"
  * )
  */
-class UnpublishNode extends FieldUpdateActionBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function getFieldsToUpdate() {
-    return ['status' => NodeInterface::NOT_PUBLISHED];
-  }
-
-}
+class UnpublishNode extends UnpublishAction {}
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index bb69bd33f5..a14f21ad84 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -2042,3 +2042,26 @@ function system_update_8403() {
     }
   }
 }
+
+/**
+ * Change plugin IDs of actions.
+ */
+function system_update_8500() {
+  $array = [
+    'comment_publish_action' => 'entity_publish_action:comment',
+    'comment_unpublish_action' => 'entity_unpublish_action:comment',
+    'comment_save_action' => 'entity_save_action:comment',
+    'node_publish_action' => 'entity_publish_action:node',
+    'node_unpublish_action' => 'entity_unpublish_action:node',
+    'node_save_action' => 'entity_save_action:node',
+  ];
+
+  $actions = \Drupal::configFactory()->listAll('system.action.');
+  foreach ($actions as $action) {
+    $config = \Drupal::configFactory()->getEditable($action);
+    if (isset($array[$config->get('plugin')])) {
+      $config->set('plugin', $array[$config->get('plugin')]);
+      $config->save();
+    }
+  }
+}
diff --git a/core/modules/system/tests/src/Functional/Update/UpdateActionsWithEntityPluginsTest.php b/core/modules/system/tests/src/Functional/Update/UpdateActionsWithEntityPluginsTest.php
new file mode 100644
index 0000000000..cabcca01e4
--- /dev/null
+++ b/core/modules/system/tests/src/Functional/Update/UpdateActionsWithEntityPluginsTest.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace Drupal\Tests\system\Functional\Update;
+
+use Drupal\FunctionalTests\Update\UpdatePathTestBase;
+use Drupal\system\Entity\Action;
+
+/**
+ * Tests the upgrade path for converting comment and node actions to generic entity ones.
+ *
+ * @group Update
+ */
+class UpdateActionsWithEntityPluginsTest extends UpdatePathTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setDatabaseDumpFiles() {
+    $this->databaseDumpFiles = [__DIR__ . '/../../../../tests/fixtures/update/drupal-8.bare.standard.php.gz'];
+  }
+
+  /**
+   * Tests the upgrade path for converting comment and node actions to generic entity ones.
+   *
+   * @see system_update_8500()
+   */
+  public function testUpdateActionsWithEntityPlugins() {
+    $array = [
+      'comment_publish_action' => ['comment_publish_action', 'entity_publish_action:comment'],
+      'comment_unpublish_action' => ['comment_unpublish_action', 'entity_unpublish_action:comment'],
+      'comment_save_action' => ['comment_save_action', 'entity_save_action:comment'],
+      'node_publish_action' => ['node_publish_action', 'entity_publish_action:node'],
+      'node_unpublish_action' => ['node_unpublish_action', 'entity_unpublish_action:node'],
+      'node_save_action' => ['node_save_action', 'entity_save_action:node'],
+    ];
+
+    foreach ($array as $key => list($before, $after)) {
+      /** @var \Drupal\system\Entity\Action $action */
+      $action = Action::load($key);
+      $this->assertSame($before, $action->getPlugin()->getPluginId());
+    }
+
+    $this->runUpdates();
+
+    foreach ($array as $key => list($before, $after)) {
+      /** @var \Drupal\system\Entity\Action $action */
+      $action = Action::load($key);
+      $this->assertSame($after, $action->getPlugin()->getPluginId());
+    }
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Action/PublishActionTest.php b/core/tests/Drupal/Tests/Core/Action/PublishActionTest.php
new file mode 100644
index 0000000000..5ea64deaea
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Action/PublishActionTest.php
@@ -0,0 +1,78 @@
+<?php
+
+namespace Drupal\Tests\Core\Action;
+
+use Drupal\Core\Entity\EntityPublishedInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Action\Plugin\Action\PublishAction;
+use Drupal\Core\Action\Plugin\Action\UnpublishAction;
+use Drupal\Core\Action\Plugin\Action\Derivative\EntityPublishedActionDeriver;
+use Drupal\node\NodeInterface;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @group Action
+ */
+class PublishActionTest extends UnitTestCase {
+
+  /**
+   * @covers \Drupal\Core\Action\Plugin\Action\Derivative\EntityPublishedActionDeriver::getDerivativeDefinitions
+   */
+  public function testGetDerivativeDefinitions() {
+    $publishable_type = $this->prophesize(EntityTypeInterface::class);
+    $publishable_type->entityClassImplements(EntityPublishedInterface::class)->willReturn(TRUE);
+    $publishable_type->id()->willReturn('publishable_type');
+    $publishable_type->getSingularLabel()->willReturn('content');
+
+    $unpublishable_type = $this->prophesize(EntityTypeInterface::class);
+    $unpublishable_type->entityClassImplements(EntityPublishedInterface::class)->willReturn(FALSE);
+
+    $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
+    $entity_type_manager->getDefinitions()->willReturn([
+      'publishable_type' => $publishable_type->reveal(),
+      'unpublishable_type' => $unpublishable_type->reveal(),
+    ]);
+
+    $deriver = new EntityPublishedActionDeriver($entity_type_manager->reveal());
+
+    $this->assertEquals([
+      'publishable_type' => [
+        'type' => 'publishable_type',
+        'label' => 'Publish content',
+        'action_label' => 'Publish',
+      ],
+    ], $deriver->getDerivativeDefinitions([
+      'action_label' => 'Publish',
+    ]));
+  }
+
+  /**
+   * @covers \Drupal\Core\Action\Plugin\Action\UnpublishAction::execute
+   */
+  public function testPublishAction() {
+    $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
+    $entity = $this->prophesize(NodeInterface::class);
+    $entity->setPublished()->shouldBeCalled();
+    $entity->setPublished()->willReturn($entity);
+    $entity->save()->shouldBeCalled();
+
+    $action_plugin = new PublishAction([], '', [], $entity_type_manager->reveal());
+    $action_plugin->execute($entity->reveal());
+  }
+
+  /**
+   * @covers \Drupal\Core\Action\Plugin\Action\PublishAction::execute
+   */
+  public function testUnpublishAction() {
+    $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
+    $entity = $this->prophesize(NodeInterface::class);
+    $entity->setUnpublished()->shouldBeCalled();
+    $entity->setUnpublished()->willReturn($entity);
+    $entity->save()->shouldBeCalled();
+
+    $action_plugin = new UnpublishAction([], '', [], $entity_type_manager->reveal());
+    $action_plugin->execute($entity->reveal());
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Action/SaveActionTest.php b/core/tests/Drupal/Tests/Core/Action/SaveActionTest.php
new file mode 100644
index 0000000000..2f5e9f5d17
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Action/SaveActionTest.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Drupal\Tests\Core\Action;
+
+use Drupal\Component\Datetime\TimeInterface;
+use Drupal\Core\Action\Plugin\Action\Derivative\EntityChangedActionDeriver;
+use Drupal\Core\Action\Plugin\Action\SaveAction;
+use Drupal\Core\Entity\EntityChangedInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\node\NodeInterface;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @group Action
+ */
+class SaveActionTest extends UnitTestCase {
+
+  /**
+   * @covers \Drupal\Core\Action\Plugin\Action\Derivative\EntityChangedActionDeriver::getDerivativeDefinitions
+   */
+  public function testGetDerivativeDefinitions() {
+    $changeable_type = $this->prophesize(EntityTypeInterface::class);
+    $changeable_type->entityClassImplements(EntityChangedInterface::class)->willReturn(TRUE);
+    $changeable_type->id()->willReturn('changeable_type');
+    $changeable_type->getSingularLabel()->willReturn('content');
+
+    $unchangeable_type = $this->prophesize(EntityTypeInterface::class);
+    $unchangeable_type->entityClassImplements(EntityChangedInterface::class)->willReturn(FALSE);
+
+    $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
+    $entity_type_manager->getDefinitions()->willReturn([
+      'changeable_type' => $changeable_type->reveal(),
+      'unchangeable_type' => $unchangeable_type->reveal(),
+    ]);
+
+    $deriver = new EntityChangedActionDeriver($entity_type_manager->reveal());
+
+    $this->assertEquals([
+      'changeable_type' => [
+        'type' => 'changeable_type',
+        'label' => 'Save content',
+        'action_label' => 'Save',
+      ],
+    ], $deriver->getDerivativeDefinitions([
+      'action_label' => 'Save',
+    ]));
+  }
+
+  /**
+   * @covers \Drupal\Core\Action\Plugin\Action\SaveAction::execute
+   */
+  public function testSaveAction() {
+    $entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
+    /** @var \Drupal\node\NodeInterface $entity */
+    $entity = $this->prophesize(NodeInterface::class);
+    $entity->setChangedTime(1)->shouldBeCalled();
+    $entity->setChangedTime(1)->willReturn($entity);
+    $entity->save()->shouldBeCalled();
+
+    $time = $this->prophesize(TimeInterface::class);
+    $time->getRequestTime()->willReturn(1);
+
+    $action_plugin = new SaveAction([], '', [], $entity_type_manager->reveal(), $time->reveal());
+    $action_plugin->execute($entity->reveal());
+  }
+
+}
