diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml
index 160b526ebd527f4d9efa5eb5082a3f39a9760785..95311ada2bbff11f3f12977e3b2c4e6ea5e0bcf2 100644
--- a/core/config/schema/core.data_types.schema.yml
+++ b/core/config/schema/core.data_types.schema.yml
@@ -213,23 +213,6 @@ theme_settings:
       sequence:
         type: theme_settings.third_party.[%key]
 
-views_field_bulk_form:
-  type: views_field
-  label: 'Bulk operation'
-  mapping:
-    action_title:
-      type: label
-      label: 'Action title'
-    include_exclude:
-      type: string
-      label: 'Available actions'
-    selected_actions:
-      type: sequence
-      label: 'Available actions'
-      sequence:
-        type: string
-        label: 'Action'
-
 # Array of routes with route_name and route_params keys.
 route:
   type: mapping
diff --git a/core/modules/comment/src/Plugin/views/field/CommentBulkForm.php b/core/modules/comment/src/Plugin/views/field/CommentBulkForm.php
index d4283734f76cea93ce3bf61182c156e67411ed3e..b67cff227ec82e2cbacf1f55fe9fa7e3e8b0b994 100644
--- a/core/modules/comment/src/Plugin/views/field/CommentBulkForm.php
+++ b/core/modules/comment/src/Plugin/views/field/CommentBulkForm.php
@@ -2,7 +2,7 @@
 
 namespace Drupal\comment\Plugin\views\field;
 
-use Drupal\system\Plugin\views\field\BulkForm;
+use Drupal\views\Plugin\views\field\BulkForm;
 
 /**
  * Defines a comment operations bulk form element.
diff --git a/core/modules/node/src/Plugin/views/field/NodeBulkForm.php b/core/modules/node/src/Plugin/views/field/NodeBulkForm.php
index e5aad5645dd65401caca9e5344a1d71fbeb3eee3..4a6e219cceb3c086cadd86286bacaf2a05a7ac2f 100644
--- a/core/modules/node/src/Plugin/views/field/NodeBulkForm.php
+++ b/core/modules/node/src/Plugin/views/field/NodeBulkForm.php
@@ -2,7 +2,7 @@
 
 namespace Drupal\node\Plugin\views\field;
 
-use Drupal\system\Plugin\views\field\BulkForm;
+use Drupal\views\Plugin\views\field\BulkForm;
 
 /**
  * Defines a node operations bulk form element.
diff --git a/core/modules/system/src/Plugin/views/field/BulkForm.php b/core/modules/system/src/Plugin/views/field/BulkForm.php
index 65fdc51cc73388ae608ee32a8f2a30bc9eaf50c2..e34e6a551ab6f77fc964d50f638ee1f9ea9b9359 100644
--- a/core/modules/system/src/Plugin/views/field/BulkForm.php
+++ b/core/modules/system/src/Plugin/views/field/BulkForm.php
@@ -2,506 +2,20 @@
 
 namespace Drupal\system\Plugin\views\field;
 
-use Drupal\Core\Cache\CacheableDependencyInterface;
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityManagerInterface;
-use Drupal\Core\Entity\RevisionableInterface;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Language\LanguageManagerInterface;
-use Drupal\Core\Routing\RedirectDestinationTrait;
-use Drupal\Core\TypedData\TranslatableInterface;
-use Drupal\views\Entity\Render\EntityTranslationRenderTrait;
-use Drupal\views\Plugin\views\display\DisplayPluginBase;
-use Drupal\views\Plugin\views\field\FieldPluginBase;
-use Drupal\views\Plugin\views\field\UncacheableFieldHandlerTrait;
-use Drupal\views\Plugin\views\style\Table;
-use Drupal\views\ResultRow;
-use Drupal\views\ViewExecutable;
-use Symfony\Component\DependencyInjection\ContainerInterface;
+@trigger_error(__NAMESPACE__ . '\BulkForm is deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0. Use \Drupal\views\Plugin\views\field\BulkForm instead. See https://www.drupal.org/node/2916716.', E_USER_DEPRECATED);
+
+use Drupal\views\Plugin\views\field\BulkForm as ViewsBulkForm;
 
 /**
  * Defines a actions-based bulk operation form element.
  *
- * @ViewsField("bulk_form")
+ * @ViewsField("legacy_bulk_form")
+ *
+ * @deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0. Use
+ *   \Drupal\views\Plugin\views\field\BulkForm instead.
+ *
+ * @see https://www.drupal.org/node/2916716
  */
-class BulkForm extends FieldPluginBase implements CacheableDependencyInterface {
-
-  use RedirectDestinationTrait;
-  use UncacheableFieldHandlerTrait;
-  use EntityTranslationRenderTrait;
-
-  /**
-   * The entity manager.
-   *
-   * @var \Drupal\Core\Entity\EntityManagerInterface
-   */
-  protected $entityManager;
-
-  /**
-   * The action storage.
-   *
-   * @var \Drupal\Core\Entity\EntityStorageInterface
-   */
-  protected $actionStorage;
-
-  /**
-   * An array of actions that can be executed.
-   *
-   * @var \Drupal\system\ActionConfigEntityInterface[]
-   */
-  protected $actions = [];
-
-  /**
-   * The language manager.
-   *
-   * @var \Drupal\Core\Language\LanguageManagerInterface
-   */
-  protected $languageManager;
-
-  /**
-   * Constructs a new BulkForm object.
-   *
-   * @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\EntityManagerInterface $entity_manager
-   *   The entity manager.
-   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
-   *   The language manager.
-   */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager) {
-    parent::__construct($configuration, $plugin_id, $plugin_definition);
-
-    $this->entityManager = $entity_manager;
-    $this->actionStorage = $entity_manager->getStorage('action');
-    $this->languageManager = $language_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.manager'),
-      $container->get('language_manager')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
-    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;
-    });
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getCacheMaxAge() {
-    // @todo Consider making the bulk operation form cacheable. See
-    //   https://www.drupal.org/node/2503009.
-    return 0;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getCacheContexts() {
-    return $this->languageManager->isMultilingual() ? $this->getEntityTranslationRenderer()->getCacheContexts() : [];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getCacheTags() {
-    return [];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getEntityTypeId() {
-    return $this->getEntityType();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function getEntityManager() {
-    return $this->entityManager;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function getLanguageManager() {
-    return $this->languageManager;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function getView() {
-    return $this->view;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function defineOptions() {
-    $options = parent::defineOptions();
-    $options['action_title'] = ['default' => $this->t('Action')];
-    $options['include_exclude'] = [
-      'default' => 'exclude',
-    ];
-    $options['selected_actions'] = [
-      'default' => [],
-    ];
-    return $options;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildOptionsForm(&$form, FormStateInterface $form_state) {
-    $form['action_title'] = [
-      '#type' => 'textfield',
-      '#title' => $this->t('Action title'),
-      '#default_value' => $this->options['action_title'],
-      '#description' => $this->t('The title shown above the actions dropdown.'),
-    ];
-
-    $form['include_exclude'] = [
-      '#type' => 'radios',
-      '#title' => $this->t('Available actions'),
-      '#options' => [
-        'exclude' => $this->t('All actions, except selected'),
-        'include' => $this->t('Only selected actions'),
-      ],
-      '#default_value' => $this->options['include_exclude'],
-    ];
-    $form['selected_actions'] = [
-      '#type' => 'checkboxes',
-      '#title' => $this->t('Selected actions'),
-      '#options' => $this->getBulkOptions(FALSE),
-      '#default_value' => $this->options['selected_actions'],
-    ];
-
-    parent::buildOptionsForm($form, $form_state);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function validateOptionsForm(&$form, FormStateInterface $form_state) {
-    parent::validateOptionsForm($form, $form_state);
-
-    $selected_actions = $form_state->getValue(['options', 'selected_actions']);
-    $form_state->setValue(['options', 'selected_actions'], array_values(array_filter($selected_actions)));
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function preRender(&$values) {
-    parent::preRender($values);
-
-    // If the view is using a table style, provide a placeholder for a
-    // "select all" checkbox.
-    if (!empty($this->view->style_plugin) && $this->view->style_plugin instanceof Table) {
-      // Add the tableselect css classes.
-      $this->options['element_label_class'] .= 'select-all';
-      // Hide the actual label of the field on the table header.
-      $this->options['label'] = '';
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getValue(ResultRow $row, $field = NULL) {
-    return '<!--form-item-' . $this->options['id'] . '--' . $row->index . '-->';
-  }
-
-  /**
-   * Form constructor for the bulk form.
-   *
-   * @param array $form
-   *   An associative array containing the structure of the form.
-   * @param \Drupal\Core\Form\FormStateInterface $form_state
-   *   The current state of the form.
-   */
-  public function viewsForm(&$form, FormStateInterface $form_state) {
-    // Make sure we do not accidentally cache this form.
-    // @todo Evaluate this again in https://www.drupal.org/node/2503009.
-    $form['#cache']['max-age'] = 0;
-
-    // Add the tableselect javascript.
-    $form['#attached']['library'][] = 'core/drupal.tableselect';
-    $use_revision = array_key_exists('revision', $this->view->getQuery()->getEntityTableInfo());
-
-    // Only add the bulk form options and buttons if there are results.
-    if (!empty($this->view->result)) {
-      // Render checkboxes for all rows.
-      $form[$this->options['id']]['#tree'] = TRUE;
-      foreach ($this->view->result as $row_index => $row) {
-        $entity = $this->getEntityTranslation($this->getEntity($row), $row);
-
-        $form[$this->options['id']][$row_index] = [
-          '#type' => 'checkbox',
-          // We are not able to determine a main "title" for each row, so we can
-          // only output a generic label.
-          '#title' => $this->t('Update this item'),
-          '#title_display' => 'invisible',
-          '#default_value' => !empty($form_state->getValue($this->options['id'])[$row_index]) ? 1 : NULL,
-          '#return_value' => $this->calculateEntityBulkFormKey($entity, $use_revision),
-        ];
-      }
-
-      // Replace the form submit button label.
-      $form['actions']['submit']['#value'] = $this->t('Apply to selected items');
-
-      // Ensure a consistent container for filters/operations in the view header.
-      $form['header'] = [
-        '#type' => 'container',
-        '#weight' => -100,
-      ];
-
-      // Build the bulk operations action widget for the header.
-      // Allow themes to apply .container-inline on this separate container.
-      $form['header'][$this->options['id']] = [
-        '#type' => 'container',
-      ];
-      $form['header'][$this->options['id']]['action'] = [
-        '#type' => 'select',
-        '#title' => $this->options['action_title'],
-        '#options' => $this->getBulkOptions(),
-      ];
-
-      // Duplicate the form actions into the action container in the header.
-      $form['header'][$this->options['id']]['actions'] = $form['actions'];
-    }
-    else {
-      // Remove the default actions build array.
-      unset($form['actions']);
-    }
-  }
-
-  /**
-   * Returns the available operations for this form.
-   *
-   * @param bool $filtered
-   *   (optional) Whether to filter actions to selected actions.
-   * @return array
-   *   An associative array of operations, suitable for a select element.
-   */
-  protected function getBulkOptions($filtered = TRUE) {
-    $options = [];
-    // Filter the action list.
-    foreach ($this->actions as $id => $action) {
-      if ($filtered) {
-        $in_selected = in_array($id, $this->options['selected_actions']);
-        // If the field is configured to include only the selected actions,
-        // skip actions that were not selected.
-        if (($this->options['include_exclude'] == 'include') && !$in_selected) {
-          continue;
-        }
-        // Otherwise, if the field is configured to exclude the selected
-        // actions, skip actions that were selected.
-        elseif (($this->options['include_exclude'] == 'exclude') && $in_selected) {
-          continue;
-        }
-      }
-
-      $options[$id] = $action->label();
-    }
-
-    return $options;
-  }
-
-  /**
-   * Submit handler for the bulk form.
-   *
-   * @param array $form
-   *   An associative array containing the structure of the form.
-   * @param \Drupal\Core\Form\FormStateInterface $form_state
-   *   The current state of the form.
-   *
-   * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
-   *   Thrown when the user tried to access an action without access to it.
-   */
-  public function viewsFormSubmit(&$form, FormStateInterface $form_state) {
-    if ($form_state->get('step') == 'views_form_views_form') {
-      // Filter only selected checkboxes. Use the actual user input rather than
-      // the raw form values array, since the site data may change before the
-      // bulk form is submitted, which can lead to data loss.
-      $user_input = $form_state->getUserInput();
-      $selected = array_filter($user_input[$this->options['id']]);
-      $entities = [];
-      $action = $this->actions[$form_state->getValue('action')];
-      $count = 0;
-
-      foreach ($selected as $bulk_form_key) {
-        $entity = $this->loadEntityFromBulkFormKey($bulk_form_key);
-
-        // Skip execution if the user did not have access.
-        if (!$action->getPlugin()->access($entity, $this->view->getUser())) {
-          $this->drupalSetMessage($this->t('No access to execute %action on the @entity_type_label %entity_label.', [
-            '%action' => $action->label(),
-            '@entity_type_label' => $entity->getEntityType()->getLabel(),
-            '%entity_label' => $entity->label()
-          ]), 'error');
-          continue;
-        }
-
-        $count++;
-
-        $entities[$bulk_form_key] = $entity;
-      }
-
-      $action->execute($entities);
-
-      $operation_definition = $action->getPluginDefinition();
-      if (!empty($operation_definition['confirm_form_route_name'])) {
-        $options = [
-          'query' => $this->getDestinationArray(),
-        ];
-        $form_state->setRedirect($operation_definition['confirm_form_route_name'], [], $options);
-      }
-      else {
-        // Don't display the message unless there are some elements affected and
-        // there is no confirmation form.
-        if ($count) {
-          drupal_set_message($this->formatPlural($count, '%action was applied to @count item.', '%action was applied to @count items.', [
-            '%action' => $action->label(),
-          ]));
-        }
-      }
-    }
-  }
-
-  /**
-   * Returns the message to be displayed when there are no selected items.
-   *
-   * @return string
-   *   Message displayed when no items are selected.
-   */
-  protected function emptySelectedMessage() {
-    return $this->t('No items selected.');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function viewsFormValidate(&$form, FormStateInterface $form_state) {
-    $selected = array_filter($form_state->getValue($this->options['id']));
-    if (empty($selected)) {
-      $form_state->setErrorByName('', $this->emptySelectedMessage());
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function query() {
-    if ($this->languageManager->isMultilingual()) {
-      $this->getEntityTranslationRenderer()->query($this->query, $this->relationship);
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function clickSortable() {
-    return FALSE;
-  }
-
-  /**
-   * Wraps drupal_set_message().
-   */
-  protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) {
-    drupal_set_message($message, $type, $repeat);
-  }
-
-  /**
-   * Calculates a bulk form key.
-   *
-   * This generates a key that is used as the checkbox return value when
-   * submitting a bulk form. This key allows the entity for the row to be loaded
-   * totally independently of the executed view row.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity to calculate a bulk form key for.
-   * @param bool $use_revision
-   *   Whether the revision id should be added to the bulk form key. This should
-   *   be set to TRUE only if the view is listing entity revisions.
-   *
-   * @return string
-   *   The bulk form key representing the entity's id, language and revision (if
-   *   applicable) as one string.
-   *
-   * @see self::loadEntityFromBulkFormKey()
-   */
-  protected function calculateEntityBulkFormKey(EntityInterface $entity, $use_revision) {
-    $key_parts = [$entity->language()->getId(), $entity->id()];
-
-    if ($entity instanceof RevisionableInterface && $use_revision) {
-      $key_parts[] = $entity->getRevisionId();
-    }
-
-    // An entity ID could be an arbitrary string (although they are typically
-    // numeric). JSON then Base64 encoding ensures the bulk_form_key is
-    // safe to use in HTML, and that the key parts can be retrieved.
-    $key = json_encode($key_parts);
-    return base64_encode($key);
-  }
-
-  /**
-   * Loads an entity based on a bulk form key.
-   *
-   * @param string $bulk_form_key
-   *   The bulk form key representing the entity's id, language and revision (if
-   *   applicable) as one string.
-   *
-   * @return \Drupal\Core\Entity\EntityInterface
-   *   The entity loaded in the state (language, optionally revision) specified
-   *   as part of the bulk form key.
-   */
-  protected function loadEntityFromBulkFormKey($bulk_form_key) {
-    $key = base64_decode($bulk_form_key);
-    $key_parts = json_decode($key);
-    $revision_id = NULL;
-
-    // If there are 3 items, vid will be last.
-    if (count($key_parts) === 3) {
-      $revision_id = array_pop($key_parts);
-    }
-
-    // The first two items will always be langcode and ID.
-    $id = array_pop($key_parts);
-    $langcode = array_pop($key_parts);
-
-    // Load the entity or a specific revision depending on the given key.
-    $storage = $this->entityManager->getStorage($this->getEntityType());
-    $entity = $revision_id ? $storage->loadRevision($revision_id) : $storage->load($id);
-
-    if ($entity instanceof TranslatableInterface) {
-      $entity = $entity->getTranslation($langcode);
-    }
-
-    return $entity;
-  }
+class BulkForm extends ViewsBulkForm {
 
 }
diff --git a/core/modules/user/src/Plugin/views/field/UserBulkForm.php b/core/modules/user/src/Plugin/views/field/UserBulkForm.php
index 3d0196a962170ba1a5b1c3190ac4edb0f2993e53..805df2b9e432721a7113c58678ac281a9e1294b7 100644
--- a/core/modules/user/src/Plugin/views/field/UserBulkForm.php
+++ b/core/modules/user/src/Plugin/views/field/UserBulkForm.php
@@ -3,8 +3,8 @@
 namespace Drupal\user\Plugin\views\field;
 
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\system\Plugin\views\field\BulkForm;
 use Drupal\user\UserInterface;
+use Drupal\views\Plugin\views\field\BulkForm;
 
 /**
  * Defines a user operations bulk form element.
diff --git a/core/modules/views/config/schema/views.data_types.schema.yml b/core/modules/views/config/schema/views.data_types.schema.yml
index a3d1b3430d8dc49a94d25694ed139f0d5c4b5f38..7736a747d83559c8ee7982f8f1e6605f7c5b2b36 100644
--- a/core/modules/views/config/schema/views.data_types.schema.yml
+++ b/core/modules/views/config/schema/views.data_types.schema.yml
@@ -820,3 +820,20 @@ views_cache:
 views_display_extender:
   type: mapping
   label: 'Display extender settings'
+
+views_field_bulk_form:
+  type: views_field
+  label: 'Bulk operation'
+  mapping:
+    action_title:
+      type: label
+      label: 'Action title'
+    include_exclude:
+      type: string
+      label: 'Available actions'
+    selected_actions:
+      type: sequence
+      label: 'Available actions'
+      sequence:
+        type: string
+        label: 'Action'
diff --git a/core/modules/views/src/Plugin/views/field/BulkForm.php b/core/modules/views/src/Plugin/views/field/BulkForm.php
new file mode 100644
index 0000000000000000000000000000000000000000..a253fc3ad53894cf4b52da222e23cfcf1b4ef2f5
--- /dev/null
+++ b/core/modules/views/src/Plugin/views/field/BulkForm.php
@@ -0,0 +1,505 @@
+<?php
+
+namespace Drupal\views\Plugin\views\field;
+
+use Drupal\Core\Cache\CacheableDependencyInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\RevisionableInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Routing\RedirectDestinationTrait;
+use Drupal\Core\TypedData\TranslatableInterface;
+use Drupal\views\Entity\Render\EntityTranslationRenderTrait;
+use Drupal\views\Plugin\views\display\DisplayPluginBase;
+use Drupal\views\Plugin\views\style\Table;
+use Drupal\views\ResultRow;
+use Drupal\views\ViewExecutable;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Defines a actions-based bulk operation form element.
+ *
+ * @ViewsField("bulk_form")
+ */
+class BulkForm extends FieldPluginBase implements CacheableDependencyInterface {
+
+  use RedirectDestinationTrait;
+  use UncacheableFieldHandlerTrait;
+  use EntityTranslationRenderTrait;
+
+  /**
+   * The entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface
+   */
+  protected $entityManager;
+
+  /**
+   * The action storage.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageInterface
+   */
+  protected $actionStorage;
+
+  /**
+   * An array of actions that can be executed.
+   *
+   * @var \Drupal\system\ActionConfigEntityInterface[]
+   */
+  protected $actions = [];
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface
+   */
+  protected $languageManager;
+
+  /**
+   * Constructs a new BulkForm object.
+   *
+   * @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\EntityManagerInterface $entity_manager
+   *   The entity manager.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+
+    $this->entityManager = $entity_manager;
+    $this->actionStorage = $entity_manager->getStorage('action');
+    $this->languageManager = $language_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.manager'),
+      $container->get('language_manager')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
+    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;
+    });
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheMaxAge() {
+    // @todo Consider making the bulk operation form cacheable. See
+    //   https://www.drupal.org/node/2503009.
+    return 0;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheContexts() {
+    return $this->languageManager->isMultilingual() ? $this->getEntityTranslationRenderer()->getCacheContexts() : [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheTags() {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getEntityTypeId() {
+    return $this->getEntityType();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEntityManager() {
+    return $this->entityManager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getLanguageManager() {
+    return $this->languageManager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getView() {
+    return $this->view;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function defineOptions() {
+    $options = parent::defineOptions();
+    $options['action_title'] = ['default' => $this->t('Action')];
+    $options['include_exclude'] = [
+      'default' => 'exclude',
+    ];
+    $options['selected_actions'] = [
+      'default' => [],
+    ];
+    return $options;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildOptionsForm(&$form, FormStateInterface $form_state) {
+    $form['action_title'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Action title'),
+      '#default_value' => $this->options['action_title'],
+      '#description' => $this->t('The title shown above the actions dropdown.'),
+    ];
+
+    $form['include_exclude'] = [
+      '#type' => 'radios',
+      '#title' => $this->t('Available actions'),
+      '#options' => [
+        'exclude' => $this->t('All actions, except selected'),
+        'include' => $this->t('Only selected actions'),
+      ],
+      '#default_value' => $this->options['include_exclude'],
+    ];
+    $form['selected_actions'] = [
+      '#type' => 'checkboxes',
+      '#title' => $this->t('Selected actions'),
+      '#options' => $this->getBulkOptions(FALSE),
+      '#default_value' => $this->options['selected_actions'],
+    ];
+
+    parent::buildOptionsForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateOptionsForm(&$form, FormStateInterface $form_state) {
+    parent::validateOptionsForm($form, $form_state);
+
+    $selected_actions = $form_state->getValue(['options', 'selected_actions']);
+    $form_state->setValue(['options', 'selected_actions'], array_values(array_filter($selected_actions)));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function preRender(&$values) {
+    parent::preRender($values);
+
+    // If the view is using a table style, provide a placeholder for a
+    // "select all" checkbox.
+    if (!empty($this->view->style_plugin) && $this->view->style_plugin instanceof Table) {
+      // Add the tableselect css classes.
+      $this->options['element_label_class'] .= 'select-all';
+      // Hide the actual label of the field on the table header.
+      $this->options['label'] = '';
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getValue(ResultRow $row, $field = NULL) {
+    return '<!--form-item-' . $this->options['id'] . '--' . $row->index . '-->';
+  }
+
+  /**
+   * Form constructor for the bulk form.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   */
+  public function viewsForm(&$form, FormStateInterface $form_state) {
+    // Make sure we do not accidentally cache this form.
+    // @todo Evaluate this again in https://www.drupal.org/node/2503009.
+    $form['#cache']['max-age'] = 0;
+
+    // Add the tableselect javascript.
+    $form['#attached']['library'][] = 'core/drupal.tableselect';
+    $use_revision = array_key_exists('revision', $this->view->getQuery()->getEntityTableInfo());
+
+    // Only add the bulk form options and buttons if there are results.
+    if (!empty($this->view->result)) {
+      // Render checkboxes for all rows.
+      $form[$this->options['id']]['#tree'] = TRUE;
+      foreach ($this->view->result as $row_index => $row) {
+        $entity = $this->getEntityTranslation($this->getEntity($row), $row);
+
+        $form[$this->options['id']][$row_index] = [
+          '#type' => 'checkbox',
+          // We are not able to determine a main "title" for each row, so we can
+          // only output a generic label.
+          '#title' => $this->t('Update this item'),
+          '#title_display' => 'invisible',
+          '#default_value' => !empty($form_state->getValue($this->options['id'])[$row_index]) ? 1 : NULL,
+          '#return_value' => $this->calculateEntityBulkFormKey($entity, $use_revision),
+        ];
+      }
+
+      // Replace the form submit button label.
+      $form['actions']['submit']['#value'] = $this->t('Apply to selected items');
+
+      // Ensure a consistent container for filters/operations in the view header.
+      $form['header'] = [
+        '#type' => 'container',
+        '#weight' => -100,
+      ];
+
+      // Build the bulk operations action widget for the header.
+      // Allow themes to apply .container-inline on this separate container.
+      $form['header'][$this->options['id']] = [
+        '#type' => 'container',
+      ];
+      $form['header'][$this->options['id']]['action'] = [
+        '#type' => 'select',
+        '#title' => $this->options['action_title'],
+        '#options' => $this->getBulkOptions(),
+      ];
+
+      // Duplicate the form actions into the action container in the header.
+      $form['header'][$this->options['id']]['actions'] = $form['actions'];
+    }
+    else {
+      // Remove the default actions build array.
+      unset($form['actions']);
+    }
+  }
+
+  /**
+   * Returns the available operations for this form.
+   *
+   * @param bool $filtered
+   *   (optional) Whether to filter actions to selected actions.
+   * @return array
+   *   An associative array of operations, suitable for a select element.
+   */
+  protected function getBulkOptions($filtered = TRUE) {
+    $options = [];
+    // Filter the action list.
+    foreach ($this->actions as $id => $action) {
+      if ($filtered) {
+        $in_selected = in_array($id, $this->options['selected_actions']);
+        // If the field is configured to include only the selected actions,
+        // skip actions that were not selected.
+        if (($this->options['include_exclude'] == 'include') && !$in_selected) {
+          continue;
+        }
+        // Otherwise, if the field is configured to exclude the selected
+        // actions, skip actions that were selected.
+        elseif (($this->options['include_exclude'] == 'exclude') && $in_selected) {
+          continue;
+        }
+      }
+
+      $options[$id] = $action->label();
+    }
+
+    return $options;
+  }
+
+  /**
+   * Submit handler for the bulk form.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+   *   Thrown when the user tried to access an action without access to it.
+   */
+  public function viewsFormSubmit(&$form, FormStateInterface $form_state) {
+    if ($form_state->get('step') == 'views_form_views_form') {
+      // Filter only selected checkboxes. Use the actual user input rather than
+      // the raw form values array, since the site data may change before the
+      // bulk form is submitted, which can lead to data loss.
+      $user_input = $form_state->getUserInput();
+      $selected = array_filter($user_input[$this->options['id']]);
+      $entities = [];
+      $action = $this->actions[$form_state->getValue('action')];
+      $count = 0;
+
+      foreach ($selected as $bulk_form_key) {
+        $entity = $this->loadEntityFromBulkFormKey($bulk_form_key);
+
+        // Skip execution if the user did not have access.
+        if (!$action->getPlugin()->access($entity, $this->view->getUser())) {
+          $this->drupalSetMessage($this->t('No access to execute %action on the @entity_type_label %entity_label.', [
+            '%action' => $action->label(),
+            '@entity_type_label' => $entity->getEntityType()->getLabel(),
+            '%entity_label' => $entity->label()
+          ]), 'error');
+          continue;
+        }
+
+        $count++;
+
+        $entities[$bulk_form_key] = $entity;
+      }
+
+      $action->execute($entities);
+
+      $operation_definition = $action->getPluginDefinition();
+      if (!empty($operation_definition['confirm_form_route_name'])) {
+        $options = [
+          'query' => $this->getDestinationArray(),
+        ];
+        $form_state->setRedirect($operation_definition['confirm_form_route_name'], [], $options);
+      }
+      else {
+        // Don't display the message unless there are some elements affected and
+        // there is no confirmation form.
+        if ($count) {
+          drupal_set_message($this->formatPlural($count, '%action was applied to @count item.', '%action was applied to @count items.', [
+            '%action' => $action->label(),
+          ]));
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns the message to be displayed when there are no selected items.
+   *
+   * @return string
+   *   Message displayed when no items are selected.
+   */
+  protected function emptySelectedMessage() {
+    return $this->t('No items selected.');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function viewsFormValidate(&$form, FormStateInterface $form_state) {
+    $selected = array_filter($form_state->getValue($this->options['id']));
+    if (empty($selected)) {
+      $form_state->setErrorByName('', $this->emptySelectedMessage());
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function query() {
+    if ($this->languageManager->isMultilingual()) {
+      $this->getEntityTranslationRenderer()->query($this->query, $this->relationship);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function clickSortable() {
+    return FALSE;
+  }
+
+  /**
+   * Wraps drupal_set_message().
+   */
+  protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) {
+    drupal_set_message($message, $type, $repeat);
+  }
+
+  /**
+   * Calculates a bulk form key.
+   *
+   * This generates a key that is used as the checkbox return value when
+   * submitting a bulk form. This key allows the entity for the row to be loaded
+   * totally independently of the executed view row.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity to calculate a bulk form key for.
+   * @param bool $use_revision
+   *   Whether the revision id should be added to the bulk form key. This should
+   *   be set to TRUE only if the view is listing entity revisions.
+   *
+   * @return string
+   *   The bulk form key representing the entity's id, language and revision (if
+   *   applicable) as one string.
+   *
+   * @see self::loadEntityFromBulkFormKey()
+   */
+  protected function calculateEntityBulkFormKey(EntityInterface $entity, $use_revision) {
+    $key_parts = [$entity->language()->getId(), $entity->id()];
+
+    if ($entity instanceof RevisionableInterface && $use_revision) {
+      $key_parts[] = $entity->getRevisionId();
+    }
+
+    // An entity ID could be an arbitrary string (although they are typically
+    // numeric). JSON then Base64 encoding ensures the bulk_form_key is
+    // safe to use in HTML, and that the key parts can be retrieved.
+    $key = json_encode($key_parts);
+    return base64_encode($key);
+  }
+
+  /**
+   * Loads an entity based on a bulk form key.
+   *
+   * @param string $bulk_form_key
+   *   The bulk form key representing the entity's id, language and revision (if
+   *   applicable) as one string.
+   *
+   * @return \Drupal\Core\Entity\EntityInterface
+   *   The entity loaded in the state (language, optionally revision) specified
+   *   as part of the bulk form key.
+   */
+  protected function loadEntityFromBulkFormKey($bulk_form_key) {
+    $key = base64_decode($bulk_form_key);
+    $key_parts = json_decode($key);
+    $revision_id = NULL;
+
+    // If there are 3 items, vid will be last.
+    if (count($key_parts) === 3) {
+      $revision_id = array_pop($key_parts);
+    }
+
+    // The first two items will always be langcode and ID.
+    $id = array_pop($key_parts);
+    $langcode = array_pop($key_parts);
+
+    // Load the entity or a specific revision depending on the given key.
+    $storage = $this->entityManager->getStorage($this->getEntityType());
+    $entity = $revision_id ? $storage->loadRevision($revision_id) : $storage->load($id);
+
+    if ($entity instanceof TranslatableInterface) {
+      $entity = $entity->getTranslation($langcode);
+    }
+
+    return $entity;
+  }
+
+}
diff --git a/core/modules/views/tests/fixtures/update/legacy-bulk-form-update.php b/core/modules/views/tests/fixtures/update/legacy-bulk-form-update.php
new file mode 100644
index 0000000000000000000000000000000000000000..e3ec6a4b92ef82fda10c3953e5626e1265233cc6
--- /dev/null
+++ b/core/modules/views/tests/fixtures/update/legacy-bulk-form-update.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * @file
+ * Test fixture.
+ */
+
+use Drupal\Core\Database\Database;
+use Drupal\Core\Serialization\Yaml;
+
+$connection = Database::getConnection();
+
+$connection->insert('config')
+  ->fields([
+    'collection' => '',
+    'name' => 'views.view.legacy_bulk_form',
+    'data' => serialize(Yaml::decode(file_get_contents(__DIR__ . '/views.view.legacy_bulk_form.yml'))),
+  ])
+  ->execute();
diff --git a/core/modules/views/tests/fixtures/update/views.view.legacy_bulk_form.yml b/core/modules/views/tests/fixtures/update/views.view.legacy_bulk_form.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5c10b289816caa19883164ebf21de65b96b70d74
--- /dev/null
+++ b/core/modules/views/tests/fixtures/update/views.view.legacy_bulk_form.yml
@@ -0,0 +1,243 @@
+uuid: 67e001ab-bf26-4317-98a0-9ef7c8e6773a
+langcode: en
+status: true
+dependencies:
+  module:
+    - node
+    - system
+    - user
+id: legacy_bulk_form
+label: 'legacy bulk form'
+module: views
+description: ''
+tag: ''
+base_table: node_field_data
+base_field: nid
+core: 8.x
+display:
+  default:
+    display_plugin: default
+    id: default
+    display_title: Master
+    position: 0
+    display_options:
+      access:
+        type: perm
+        options:
+          perm: 'access content'
+      cache:
+        type: tag
+        options: {  }
+      query:
+        type: views_query
+        options:
+          disable_sql_rewrite: false
+          distinct: false
+          replica: false
+          query_comment: ''
+          query_tags: {  }
+      exposed_form:
+        type: basic
+        options:
+          submit_button: Apply
+          reset_button: false
+          reset_button_label: Reset
+          exposed_sorts_label: 'Sort by'
+          expose_sort_order: true
+          sort_asc_label: Asc
+          sort_desc_label: Desc
+      pager:
+        type: mini
+        options:
+          items_per_page: 10
+          offset: 0
+          id: 0
+          total_pages: null
+          expose:
+            items_per_page: false
+            items_per_page_label: 'Items per page'
+            items_per_page_options: '5, 10, 25, 50'
+            items_per_page_options_all: false
+            items_per_page_options_all_label: '- All -'
+            offset: false
+            offset_label: Offset
+          tags:
+            previous: ‹‹
+            next: ››
+      style:
+        type: default
+        options:
+          grouping: {  }
+          row_class: ''
+          default_row_class: true
+          uses_fields: false
+      row:
+        type: fields
+        options:
+          inline: {  }
+          separator: ''
+          hide_empty: false
+          default_field_elements: true
+      fields:
+        nid:
+          id: nid
+          table: node_field_data
+          field: nid
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          click_sort_column: value
+          type: number_integer
+          settings:
+            thousand_separator: ''
+            prefix_suffix: true
+          group_column: value
+          group_columns: {  }
+          group_rows: true
+          delta_limit: 0
+          delta_offset: 0
+          delta_reversed: false
+          delta_first_last: false
+          multi_type: separator
+          separator: ', '
+          field_api_classes: false
+          entity_type: node
+          entity_field: nid
+          plugin_id: field
+        node_bulk_form:
+          id: node_bulk_form
+          table: node
+          field: node_bulk_form
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: ''
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          action_title: Action
+          include_exclude: exclude
+          selected_actions: {  }
+          entity_type: node
+          plugin_id: bulk_form
+      filters:
+        status:
+          value: '1'
+          table: node_field_data
+          field: status
+          plugin_id: boolean
+          entity_type: node
+          entity_field: status
+          id: status
+          expose:
+            operator: ''
+          group: 1
+      sorts:
+        created:
+          id: created
+          table: node_field_data
+          field: created
+          order: DESC
+          entity_type: node
+          entity_field: created
+          plugin_id: date
+          relationship: none
+          group_type: group
+          admin_label: ''
+          exposed: false
+          expose:
+            label: ''
+          granularity: second
+      header: {  }
+      footer: {  }
+      empty: {  }
+      relationships: {  }
+      arguments: {  }
+      display_extenders: {  }
+    cache_metadata:
+      max-age: 0
+      contexts:
+        - 'languages:language_content'
+        - 'languages:language_interface'
+        - url.query_args
+        - 'user.node_grants:view'
+        - user.permissions
+      tags: {  }
diff --git a/core/modules/views/tests/src/Functional/Update/LegacyBulkFormUpdateTest.php b/core/modules/views/tests/src/Functional/Update/LegacyBulkFormUpdateTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..75e2b54b65ec7028f16a8545f502eb76d4c89823
--- /dev/null
+++ b/core/modules/views/tests/src/Functional/Update/LegacyBulkFormUpdateTest.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\Tests\views\Functional\Update;
+
+use Drupal\FunctionalTests\Update\UpdatePathTestBase;
+use Drupal\views\Entity\View;
+
+/**
+ * Tests Views image style dependencies update.
+ *
+ * @group views
+ */
+class LegacyBulkFormUpdateTest extends UpdatePathTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setDatabaseDumpFiles() {
+    $this->databaseDumpFiles = [
+      __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
+      __DIR__ . '/../../../fixtures/update/legacy-bulk-form-update.php'
+    ];
+  }
+
+  /**
+   * Tests the updating of dependencies for Views using the bulk_form plugin.
+   */
+  public function testBulkFormDependencies() {
+    $module_dependencies = View::load('legacy_bulk_form')->getDependencies()['module'];
+
+    $this->assertTrue(in_array('system', $module_dependencies));
+
+    $this->runUpdates();
+
+    $module_dependencies = View::load('legacy_bulk_form')->getDependencies()['module'];
+
+    $this->assertFalse(in_array('system', $module_dependencies));
+  }
+
+}
diff --git a/core/modules/views/views.post_update.php b/core/modules/views/views.post_update.php
index 28c0f5c7ad9b3f2e109cdbf86943c5464adf7885..c41ae7f67d226ad5af0ea9f9001a718062f4cb0f 100644
--- a/core/modules/views/views.post_update.php
+++ b/core/modules/views/views.post_update.php
@@ -256,3 +256,17 @@ function views_post_update_entity_link_url() {
     }
   }
 }
+
+/**
+ * Update dependencies for moved bulk field plugin.
+ */
+function views_post_update_bulk_field_moved() {
+  $views = View::loadMultiple();
+  array_walk($views, function (View $view) {
+    $old_dependencies = $view->getDependencies();
+    $new_dependencies = $view->calculateDependencies()->getDependencies();
+    if ($old_dependencies !== $new_dependencies) {
+      $view->save();
+    }
+  });
+}