diff --git a/actions_permissions.module b/actions_permissions.module
index 5987d1e..8b120b8 100644
--- a/actions_permissions.module
+++ b/actions_permissions.module
@@ -8,7 +8,7 @@ function actions_permissions_permission() {
   $actions = actions_list() + _actions_permissions_advanced_actions_list();
   foreach ($actions as $callback => $action) {
     $key = !empty($action['key']) ? $action['key'] : $callback;
-    $permission = actions_permissions_get_perm($action['label'], $callback);
+    $permission = actions_permissions_get_perm($action['label'], $key);
 
     $permissions[$permission] = array(
       'title' => t('Execute %label', array('%label' => $action['label'])),
diff --git a/plugins/operation_types/action.class.php b/plugins/operation_types/action.class.php
index 4758152..e564be6 100644
--- a/plugins/operation_types/action.class.php
+++ b/plugins/operation_types/action.class.php
@@ -47,7 +47,7 @@ class ViewsBulkOperationsAction extends ViewsBulkOperationsBaseOperation {
   public function access($account) {
     // Use actions_permissions if enabled.
     if (module_exists('actions_permissions')) {
-      $perm = actions_permissions_get_perm($this->operationInfo['label'], $this->operationInfo['callback']);
+      $perm = actions_permissions_get_perm($this->operationInfo['label'], $this->operationInfo['key']);
       if (!user_access($perm, $account)) {
         return FALSE;
       }
@@ -76,6 +76,7 @@ class ViewsBulkOperationsAction extends ViewsBulkOperationsBaseOperation {
    *   An array of related data provided by the caller.
    */
   public function form($form, &$form_state, array $context) {
+    $context['settings'] = $this->getAdminOption('settings', array());
     $form_callback = $this->operationInfo['callback'] . '_form';
     return $form_callback($context);
   }
@@ -90,6 +91,12 @@ class ViewsBulkOperationsAction extends ViewsBulkOperationsBaseOperation {
    *   An array containing the current state of the form.
    */
   public function formValidate($form, &$form_state) {
+    // Some modules (including this one) place their action callbacks
+    // into separate files. At this point those files might no longer be
+    // included due to a page reload, so we call actions_list() to trigger
+    // inclusion. The same thing is done by actions_do() on execute.
+    actions_list();
+
     $validation_callback = $this->operationInfo['callback'] . '_validate';
     if (function_exists($validation_callback)) {
       $validation_callback($form, $form_state);
@@ -107,11 +114,104 @@ class ViewsBulkOperationsAction extends ViewsBulkOperationsBaseOperation {
    *   An array containing the current state of the form.
    */
   public function formSubmit($form, &$form_state) {
+    // Some modules (including this one) place their action callbacks
+    // into separate files. At this point those files might no longer be
+    // included due to a page reload, so we call actions_list() to trigger
+    // inclusion. The same thing is done by actions_do() on execute.
+    actions_list();
+
     $submit_callback = $this->operationInfo['callback'] . '_submit';
     $this->formOptions = $submit_callback($form, $form_state);
   }
 
   /**
+   * Returns the admin options form for the operation.
+   *
+   * The admin options form is embedded into the VBO field settings and used
+   * to configure operation behavior. The options can later be fetched
+   * through the getAdminOption() method.
+   *
+   * @param $dom_id
+   *   The dom path to the level where the admin options form is embedded.
+   *   Needed for #dependency.
+   */
+  public function adminOptionsForm($dom_id) {
+    $form = parent::adminOptionsForm($dom_id);
+
+    $settings_form_callback = $this->operationInfo['callback'] . '_views_bulk_operations_form';
+    if (function_exists($settings_form_callback)) {
+      $settings = $this->getAdminOption('settings', array());
+
+      $form['settings'] = array(
+        '#type' => 'fieldset',
+        '#title' => t('Operation settings'),
+        '#collapsible' => TRUE,
+        '#dependency' => array(
+          $dom_id . '-selected' => array(1),
+        ),
+      );
+      $form['settings'] += $settings_form_callback($settings);
+    }
+
+    return $form;
+  }
+
+  /**
+   * Validates the admin options form.
+   *
+   * @param $form
+   *   The admin options form.
+   * @param $form_state
+   *   An array containing the current state of the form. Note that this array
+   *   is constructed by the VBO views field handler, so it's not a real form
+   *   state, it contains only the 'values' key.
+   * @param $error_element_base
+   *   The base to prepend to field names when using form_set_error().
+   *   Needed because the admin settings form is embedded into a bigger form.
+   */
+  public function adminOptionsFormValidate($form, &$form_state, $error_element_base) {
+    parent::adminOptionsFormValidate($form, $form_state, $error_element_base);
+
+    if (!empty($form['settings'])) {
+      $settings_validation_callback = $this->operationInfo['callback'] . '_views_bulk_operations_form_validate';
+      if (function_exists($settings_validation_callback)) {
+        $fake_form = $form['settings'];
+        $fake_form_state = array('values' => &$form_state['values']['settings']);
+        $error_element_base .= 'settings][';
+
+        $settings_validation_callback($fake_form, $fake_form_state, $error_element_base);
+      }
+    }
+  }
+
+  /**
+   * Handles the submitted admin options form.
+   * Note that there is no need to handle saving the options, that is done
+   * by the VBO views field handler, which also injects the options into the
+   * operation object upon instantiation.
+   *
+   * @param $form
+   *   The admin options form.
+   * @param $form_state
+   *   An array containing the current state of the form. Note that this array
+   *   is constructed by the VBO views field handler, so it's not a real form
+   *   state, it contains only the 'values' key.
+   */
+  public function adminOptionsFormSubmit($form, &$form_state) {
+    parent::adminOptionsFormSubmit($form, $form_state);
+
+    if (!empty($form['settings'])) {
+      $settings_submit_callback = $this->operationInfo['callback'] . '_views_bulk_operations_form_submit';
+      if (function_exists($settings_submit_callback)) {
+        $fake_form = $form['settings'];
+        $fake_form_state = array('values' => &$form_state['values']['settings']);
+
+        $settings_submit_callback($form, $form_state);
+      }
+    }
+  }
+
+  /**
    * Returns whether the operation needs the full selected views rows to be
    * passed to execute() as a part of $context.
    */
diff --git a/plugins/operation_types/action.inc b/plugins/operation_types/action.inc
index 61a6cc5..9d9db3f 100644
--- a/plugins/operation_types/action.inc
+++ b/plugins/operation_types/action.inc
@@ -14,26 +14,50 @@ $plugin = array(
   ),
 );
 
-function views_bulk_operations_operation_action_list() {
-  $action_operations = actions_list() + views_bulk_operations_operation_advanced_action_list();
-  $operations = array();
-  foreach ($action_operations as $callback => $operation) {
-    $key = isset($operation['key']) ? $operation['key'] : $callback;
-    $operations[$key] = array(
-      'plugin' => 'action',
-      'key' => $key,
-      'label' => isset($operation['label']) ? $operation['label'] : '',
-      'callback' => $callback,
-      'parameters' => isset($operation['parameters']) ? $operation['parameters'] : array(),
-      'configurable' => !empty($operation['configurable']),
-      'type' => $operation['type'],
-      'aggregate' => !empty($operation['aggregate']),
-      'behavior' => isset($operation['behavior']) ? $operation['behavior'] : array(),
-      'permissions' => isset($operation['permissions']) ? $operation['permissions'] : NULL,
-      'pass rows' => !empty($operation['pass rows']),
-    );
+/**
+ * Returns a prepared list of available actions.
+ *
+ * Actions are fetched by invoking hook_action_info() and by loading
+ * advanced actions from the database.
+ *
+ * @param $operation_id
+ *   The full, prefixed operation_id of the operation (in this case, action)
+ *   to return, or NULL to return an array with all operations.
+ */
+function views_bulk_operations_operation_action_list($operation_id = NULL) {
+  $operations = &drupal_static(__FUNCTION__);
+
+  if (!isset($operations)) {
+    $actions_list = actions_list() + views_bulk_operations_operation_advanced_action_list();
+    $operations = array();
+    foreach ($actions_list as $callback => $action) {
+      $key = isset($action['key']) ? $action['key'] : $callback;
+      // All operations must be prefixed with the operation_type.
+      $new_operation_id = 'action::' . $key;
+
+      $operations[$new_operation_id] = array(
+        'operation_type' => 'action',
+        'type' => $action['type'],
+        // Keep the unprefixed key around, for use in admin labels and action permissions.
+        'key' => $key,
+        'callback' => $callback,
+        'label' => isset($action['label']) ? $action['label'] : '',
+        'parameters' => isset($action['parameters']) ? $action['parameters'] : array(),
+        'configurable' => !empty($action['configurable']),
+        'aggregate' => !empty($action['aggregate']),
+        'behavior' => isset($action['behavior']) ? $action['behavior'] : array(),
+        'permissions' => isset($action['permissions']) ? $action['permissions'] : NULL,
+        'pass rows' => !empty($action['pass rows']),
+      );
+    }
+  }
+
+  if (isset($operation_id)) {
+    return isset($operations[$operation_id]) ? $operations[$operation_id] : FALSE;
+  }
+  else {
+    return $operations;
   }
-  return $operations;
 }
 
 /**
diff --git a/plugins/operation_types/base.class.php b/plugins/operation_types/base.class.php
index ee49634..35bf1d3 100644
--- a/plugins/operation_types/base.class.php
+++ b/plugins/operation_types/base.class.php
@@ -61,6 +61,13 @@ abstract class ViewsBulkOperationsBaseOperation {
   }
 
   /**
+   * Returns the name of the operation_type plugin that provides the operation.
+   */
+  public function type() {
+    return $this->operationInfo['operation_type'];
+  }
+
+  /**
    * Returns the human-readable name of the operation, meant to be shown
    * to the user.
    */
@@ -77,6 +84,15 @@ abstract class ViewsBulkOperationsBaseOperation {
   }
 
   /**
+   * Returns the human-readable name of the operation, meant to be shown
+   * to the admin.
+   */
+  public function adminLabel() {
+    $label = $this->operationInfo['label'] . ' (' . $this->operationInfo['key'] . ')';
+    return $label;
+  }
+
+  /**
    * Returns whether the operation is configurable. Used to determine
    * whether the operation's form methods should be invoked.
    */
@@ -130,6 +146,82 @@ abstract class ViewsBulkOperationsBaseOperation {
   abstract function formSubmit($form, &$form_state);
 
   /**
+   * Returns the admin options form for the operation.
+   *
+   * The admin options form is embedded into the VBO field settings and used
+   * to configure operation behavior. The options can later be fetched
+   * through the getAdminOption() method.
+   *
+   * @param $dom_id
+   *   The dom path to the level where the admin options form is embedded.
+   *   Needed for #dependency.
+   */
+  public function adminOptionsForm($dom_id) {
+    $label = $this->getAdminOption('label', '');
+
+    $form = array();
+    $form['override_label'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Override label'),
+      '#default_value' => $label !== '',
+      '#dependency' => array(
+        $dom_id . '-selected' => array(1),
+      ),
+    );
+    $form['label'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Provide label'),
+      '#title_display' => 'invisible',
+      '#default_value' => $label,
+      '#dependency' => array(
+        $dom_id . '-selected' => array(1),
+        $dom_id . '-override-label' => array(1),
+      ),
+      '#dependency_count' => 2,
+    );
+
+    return $form;
+  }
+
+  /**
+   * Validates the admin options form.
+   *
+   * @param $form
+   *   The admin options form.
+   * @param $form_state
+   *   An array containing the current state of the form. Note that this array
+   *   is constructed by the VBO views field handler, so it's not a real form
+   *   state, it contains only the 'values' key.
+   * @param $error_element_base
+   *   The base to prepend to field names when using form_set_error().
+   *   Needed because the admin options form is embedded into a bigger form.
+   */
+  public function adminOptionsFormValidate($form, &$form_state, $error_element_base) {
+    // No need to do anything, but make the function have a body anyway
+    // so that it's callable by overriding methods.
+  }
+
+  /**
+   * Handles the submitted admin options form.
+   * Note that there is no need to handle saving the options, that is done
+   * by the VBO views field handler, which also injects the options into the
+   * operation object upon instantiation.
+   *
+   * @param $form
+   *   The admin options form.
+   * @param $form_state
+   *   An array containing the current state of the form. Note that this array
+   *   is constructed by the VBO views field handler, so it's not a real form
+   *   state, it contains only the 'values' key.
+   */
+  public function adminOptionsFormSubmit($form, &$form_state) {
+    // If the "Override label" checkbox was deselected, clear the entered value.
+    if (empty($form_state['values']['override_label'])) {
+      $form_state['values']['label'] = '';
+    }
+  }
+
+  /**
    * Returns whether the selected entities should be aggregated
    * (loaded in bulk and passed in together).
    * To be avoided if possible, since aggregation makes it impossible to use
diff --git a/plugins/operation_types/rules_component.inc b/plugins/operation_types/rules_component.inc
index 94a1e74..e336d8a 100644
--- a/plugins/operation_types/rules_component.inc
+++ b/plugins/operation_types/rules_component.inc
@@ -14,7 +14,14 @@ $plugin = array(
   ),
 );
 
-function views_bulk_operations_operation_rules_component_list() {
+/**
+ * Returns a prepared list of available rules components.
+ *
+ * @param $operation_id
+ *   The full, prefixed operation_id of the operation (in this case, rules
+ *   component) to return, or NULL to return an array with all operations.
+ */
+function views_bulk_operations_operation_rules_component_list($operation_id = NULL) {
   if (!module_exists('rules')) {
     return array();
   }
@@ -29,8 +36,17 @@ function views_bulk_operations_operation_rules_component_list() {
     $list_types[] = "list<$type>";
   }
 
+  $components = array();
+  if (isset($operation_id)) {
+    $id_fragments = explode('::', $operation_id);
+    $components[$id_fragments[1]] = rules_config_load($id_fragments[1]);
+  }
+  else {
+    $components = rules_get_components(FALSE, 'action');
+  }
+
   $operations = array();
-  foreach (rules_get_components(FALSE, 'action') as $component_key => $component) {
+  foreach ($components as $name => $component) {
     $parameter_info = $component->parameterInfo();
     $first_parameter = reset($parameter_info);
     $parameter_keys = array_keys($parameter_info);
@@ -51,16 +67,24 @@ function views_bulk_operations_operation_rules_component_list() {
       $aggregate = FALSE;
     }
 
-    $operations[$component_key] = array(
-      'plugin' => 'rules_component',
-      'key' => $component_key,
+    // All operations must be prefixed with the operation_type.
+    $new_operation_id = 'rules_component::' . $name;
+    $operations[$new_operation_id] = array(
+      'operation_type' => 'rules_component',
+      // Keep the unprefixed key around, for use in admin labels.
+      'key' => $name,
       'label' => $component->label,
-      'parameters' => array('component_key' => $component_key, 'entity_key' => $entity_key),
+      'parameters' => array('component_key' => $name, 'entity_key' => $entity_key),
       'configurable' => count($parameter_info) > 1,
       'type' => $type,
       'aggregate' => $aggregate,
     );
   }
 
-  return $operations;
+  if (isset($operation_id)) {
+    return isset($operations[$operation_id]) ? $operations[$operation_id] : FALSE;
+  }
+  else {
+    return $operations;
+  }
 }
diff --git a/views/views_bulk_operations_handler_field_operations.inc b/views/views_bulk_operations_handler_field_operations.inc
index 86406e0..db5edc0 100644
--- a/views/views_bulk_operations_handler_field_operations.inc
+++ b/views/views_bulk_operations_handler_field_operations.inc
@@ -7,22 +7,36 @@
 */
 
 class views_bulk_operations_handler_field_operations extends views_handler_field {
-  var $all_operations = array();
   var $revision = FALSE;
 
   function init(&$view, &$options) {
     parent::init($view, $options);
 
-    $this->populate_operations();
-
     // Update old settings.
     if (!empty($options['vbo']['selected_operations'])) {
-      foreach (array_filter($options['vbo']['selected_operations']) as $key) {
-        $this->options['vbo']['operations'][$key]['selected'] = TRUE;
-        $this->options['vbo']['operations'][$key]['skip_confirmation'] = $options['vbo']['skip_confirmation'];
+      foreach (array_filter($options['vbo']['selected_operations']) as $operation_id) {
+        $this->options['vbo']['operations'][$operation_id]['selected'] = TRUE;
+        $this->options['vbo']['operations'][$operation_id]['skip_confirmation'] = $options['vbo']['skip_confirmation'];
       }
       unset($this->options['vbo']['selected_operations']);
     }
+    // Prefix all un-prefixed operations.
+    foreach ($this->options['vbo']['operations'] as $operation_id => &$operation_options) {
+      if (strpos($operation_id, '::') === FALSE) {
+        $operations = views_bulk_operations_get_operation_info();
+        // Basically, guess.
+        foreach (array('action', 'rules_component') as $operation_type) {
+          $new_operation_id = $operation_type . '::' . $operation_id;
+          if (isset($operations[$new_operation_id])) {
+            $this->options['vbo']['operations'][$new_operation_id] = $operation_options;
+            break;
+          }
+        }
+
+        // Remove the old operation in any case.
+        unset($this->options['vbo']['operations'][$operation_id]);
+      }
+    }
   }
 
   function option_definition() {
@@ -100,106 +114,71 @@ class views_bulk_operations_handler_field_operations extends views_handler_field
       '#collapsible' => TRUE,
       '#collapsed' => FALSE,
     );
-    foreach ($this->get_operations_options() as $key => $label) {
-      $options = isset($this->options['vbo']['operations'][$key]) ? $this->options['vbo']['operations'][$key] : array();
 
-      $dom_id = 'edit-options-vbo-operations-' . str_replace(array('_', ':'), array('-', ''), $key);
-      $form['vbo']['operations'][$key]['selected'] = array(
+    $entity_type = $this->get_entity_type();
+    $options = $this->options['vbo']['operations'];
+    foreach (views_bulk_operations_get_applicable_operations($entity_type, $options) as $operation_id => $operation) {
+      $operation_options = isset($options[$operation_id]) ? $options[$operation_id] : array();
+
+      $dom_id = 'edit-options-vbo-operations-' . str_replace(array('_', ':'), array('-', ''), $operation_id);
+      $form['vbo']['operations'][$operation_id]['selected'] = array(
         '#type' => 'checkbox',
-        '#title' => $label,
-        '#default_value' => !empty($options['selected']),
+        '#title' => $operation->adminLabel(),
+        '#default_value' => !empty($operation_options['selected']),
       );
-      if (!$this->all_operations[$key]['aggregate']) {
-        $form['vbo']['operations'][$key]['use_queue'] = array(
+      if (!$operation->aggregate()) {
+        $form['vbo']['operations'][$operation_id]['use_queue'] = array(
           '#type' => 'checkbox',
           '#title' => t('Enqueue the operation instead of executing it directly'),
-          '#default_value' => !empty($options['use_queue']),
+          '#default_value' => !empty($operation_options['use_queue']),
           '#dependency' => array(
             $dom_id . '-selected' => array(1),
           ),
         );
       }
-      $form['vbo']['operations'][$key]['skip_confirmation'] = array(
+      $form['vbo']['operations'][$operation_id]['skip_confirmation'] = array(
         '#type' => 'checkbox',
         '#title' => t('Skip confirmation step'),
-        '#default_value' => !empty($options['skip_confirmation']),
-        '#dependency' => array(
-          $dom_id . '-selected' => array(1),
-        ),
-      );
-      $show_label = isset($options['label']) ? $options['label'] : '';
-      $form['vbo']['operations'][$key]['override_label'] = array(
-        '#type' => 'checkbox',
-        '#title' => t('Override label'),
-        '#default_value' => $show_label !== '',
-        '#dependency' => array(
-          $dom_id . '-selected' => array(1),
-        ),
-      );
-      $form['vbo']['operations'][$key]['label'] = array(
-        '#type' => 'textfield',
-        '#title' => t('Provide label'),
-        '#title_display' => 'invisible',
-        '#default_value' => $show_label,
+        '#default_value' => !empty($operation_options['skip_confirmation']),
         '#dependency' => array(
           $dom_id . '-selected' => array(1),
-          $dom_id . '-override-label' => array(1),
         ),
-        '#dependency_count' => 2,
       );
-      $form_function = $this->all_operations[$key]['callback'] . '_views_bulk_operations_form';
-      if (function_exists($form_function)) {
-        $settings = isset($options['settings']) ? $options['settings'] : array();
-        $form_settings = call_user_func($form_function, $settings);
-        $form['vbo']['operations'][$key]['settings'] = array(
-          '#type' => 'fieldset',
-          '#title' => t('Operation settings'),
-          '#collapsible' => TRUE,
-          '#dependency' => array(
-            $dom_id . '-selected' => array(1),
-          ),
-        );
-        $form['vbo']['operations'][$key]['settings'] += $form_settings;
-      }
+
+      $form['vbo']['operations'][$operation_id] += $operation->adminOptionsForm($dom_id);
     }
   }
 
   function options_validate(&$form, &$form_state) {
     parent::options_validate($form, $form_state);
 
-    foreach ($form_state['values']['options']['vbo']['operations'] as $key => &$options) {
-      if (empty($options['selected']) || !isset($options['settings'])) {
+    $entity_type = $this->get_entity_type();
+    foreach ($form_state['values']['options']['vbo']['operations'] as $operation_id => &$options) {
+      if (empty($options['selected'])) {
         continue;
       }
-      $operation = $this->all_operations[$key];
-      $form_function = $operation['callback'] . '_views_bulk_operations_form_validate';
-      if (function_exists($form_function)) {
-        $options['settings']['_error_element_base'] = 'vbo][operations][' . $key . '][settings][';
-        call_user_func($form_function, $form, array('values' => $options['settings']));
-      }
+
+      $operation = views_bulk_operations_get_operation($operation_id, $entity_type, $options);
+      $fake_form = $form['vbo']['operations'][$operation_id];
+      $fake_form_state = array('values' => &$options);
+      $error_element_base = 'vbo][operations][' . $operation_id . '][';
+      $operation->adminOptionsFormValidate($fake_form, $fake_form_state, $error_element_base);
     }
   }
 
   function options_submit(&$form, &$form_state) {
     parent::options_submit($form, $form_state);
 
-    foreach ($form_state['values']['options']['vbo']['operations'] as $key => &$options) {
+    $entity_type = $this->get_entity_type();
+    foreach ($form_state['values']['options']['vbo']['operations'] as $operation_id => &$options) {
       if (empty($options['selected'])) {
         continue;
       }
-      // If the "Override label" checkbox was deselected, clear the entered value.
-      if (empty($options['override_label'])) {
-        $options['label'] = '';
-      }
-      // This action has its own settings, call the submit function that
-      // should handle them (if any).
-      if (!empty($options['settings'])) {
-        $operation = $this->all_operations[$key];
-        $form_function = $operation['callback'] . '_views_bulk_operations_form_submit';
-        if (function_exists($form_function)) {
-          call_user_func($form_function, $form, array('values' => $options['settings']));
-        }
-      }
+
+      $operation = views_bulk_operations_get_operation($operation_id, $entity_type, $options);
+      $fake_form = $form['vbo']['operations'][$operation_id];
+      $fake_form_state = array('values' => &$options);
+      $operation->adminOptionsFormSubmit($fake_form, $fake_form_state);
     }
   }
 
@@ -255,77 +234,39 @@ class views_bulk_operations_handler_field_operations extends views_handler_field
     }
   }
 
-  function get_selected_operations() {
+  public function get_selected_operations() {
     global $user;
 
+    $entity_type = $this->get_entity_type();
     $selected = array();
-    foreach ($this->options['vbo']['operations'] as $key => $options) {
-      if (empty($options['selected']) || !isset($this->all_operations[$key])) {
+    foreach ($this->options['vbo']['operations'] as $operation_id => $options) {
+      if (empty($options['selected'])) {
         continue;
       }
 
-      $operation = $this->get_operation($key);
-      if (!$operation->access($user)) {
+      $operation = views_bulk_operations_get_operation($operation_id, $entity_type, $options);
+      if (!$operation || !$operation->access($user)) {
         continue;
       }
-      $selected[$key] = $operation->label();
-    }
-    return $selected;
-  }
-
-  function get_operation($key) {
-    if (empty($this->all_operations[$key])) {
-      return NULL;
+      $selected[$operation_id] = $operation;
     }
 
-    $operations = &drupal_static(__FUNCTION__);
-    if (!isset($operations[$key])) {
-      $operation_info = $this->all_operations[$key];
-      $entity_type = $this->get_entity_type();
-      $plugin = views_bulk_operations_get_operation_type($operation_info['plugin']);
-
-      $operations[$key] = new $plugin['handler']['class']($entity_type, $operation_info, $this->options['vbo']['operations'][$key]);
-    }
-    return $operations[$key];
-  }
-
-  private function get_operations_options() {
-    $options = array();
-    $entity_type = $this->get_entity_type();
-    foreach ($this->all_operations as $key => $operation) {
-      if ($operation['type'] == 'entity' || $operation['type'] == 'system') { // Actions that accept any entity type.
-        $operation['type'] = $entity_type;
-      }
-      if ($operation['type'] == $entity_type) {
-        $options[$key] = $operation['label'] .' ('. $key .')';
-      }
-    }
-    return $options;
+    return $selected;
   }
 
-  private function populate_operations() {
-    // The operations have already been populated.
-    if (!empty($this->all_operations)) {
-      return;
-    }
-
-    $operations = array();
-    $plugins = views_bulk_operations_get_operation_types();
-    foreach ($plugins as $plugin) {
-      if (isset($plugin['list callback']) && function_exists($plugin['list callback'])) {
-        $operations += $plugin['list callback']();
-      }
-    }
-
-    uasort($operations, create_function('$a, $b', 'return strcasecmp($a["label"], $b["label"]);'));
-    $this->all_operations = $operations;
+  /**
+   *  Returns the options stored for the provided operation id.
+   */
+  public function get_operation_options($operation_id) {
+    $options = $this->options['vbo']['operations'];
+    return isset($options[$operation_id]) ? $options[$operation_id] : array();
   }
 
   /**
    * Determine the base table of the VBO field, and then use it to determine
    * the entity type that VBO is operating on.
    */
-  function get_entity_type() {
+  public function get_entity_type() {
     $base_table = $this->view->base_table;
 
     // If the current field is under a relationship you can't be sure that the
diff --git a/views_bulk_operations.drush.inc b/views_bulk_operations.drush.inc
index e9dca04..8604434 100644
--- a/views_bulk_operations.drush.inc
+++ b/views_bulk_operations.drush.inc
@@ -25,15 +25,15 @@ function views_bulk_operations_drush_command() {
     'description' => 'Execute a bulk operation based on a Views Bulk Operations (VBO) view.',
     'arguments' => array(
       'vid' => 'ID or name of the view to be executed.',
-      'operation' => 'Callback name of the operation to be applied on the view results.',
+      'operation_id' => 'ID of the operation to be applied on the view results.',
       'type:[name=]value ...' => 'Parameters to be passed as view input filters, view arguments or operation arguments, where type is respectively {input, argument, operation}.',
     ),
     'examples' => array(
-      '$ drush vbo-execute admin_content node_publish_action' =>
+      '$ drush vbo-execute action::admin_content node_publish_action' =>
         'Publish nodes returned by view admin_content.',
-      '$ drush vbo-execute 44 node_assign_owner_action operation:owner_uid=3' =>
+      '$ drush vbo-execute 44 action::node_assign_owner_action operation:owner_uid=3' =>
         'Change node ownership on nodes returned by view #44, passing argument owner_uid=3 to the action.',
-      '$ drush vbo-execute admin_content node_unpublish_action input:type=expense argument:3' =>
+      '$ drush vbo-execute admin_content action::node_unpublish_action input:type=expense argument:3' =>
         'Unpublish nodes returned by view admin_content, filtering results of type expense and passing value 3 as first view argument.',
     ),
   );
@@ -56,8 +56,8 @@ function views_bulk_operations_drush_list() {
     $vbo = _views_bulk_operations_get_field($view);
     if ($vbo) {
       $operations = array();
-      foreach ($vbo->get_selected_operations() as $operation => $label) {
-        $operations[] = $label .' ('. $operation .')';
+      foreach ($vbo->get_selected_operations() as $operation_id => $operation) {
+        $operations[] = $operation->label() .' ('. $operation_id .')';
       }
       $operations[] = "---------------";
       $rows[] = array(
@@ -74,13 +74,13 @@ function views_bulk_operations_drush_list() {
 /**
  * Implementation of 'vbo execute' command.
  */
-function views_bulk_operations_drush_execute($vid = NULL, $operation = NULL) {
+function views_bulk_operations_drush_execute($vid = NULL, $operation_id = NULL) {
   // Parse arguments.
   if (is_null($vid)) {
     drush_set_error('VIEWS_BULK_OPERATIONS_MISSING_VID', dt('Please specify a view ID to execute.'));
     return;
   }
-  if (is_null($operation)) {
+  if (is_null($operation_id)) {
     drush_set_error('VIEWS_BULK_OPERATIONS_MISSING_OPERATION', dt('Please specify an operation to execute.'));
     return;
   }
@@ -116,5 +116,5 @@ function views_bulk_operations_drush_execute($vid = NULL, $operation = NULL) {
   drupal_save_session(FALSE);
 
   // Execute the VBO.
-  views_bulk_operations_execute($vid, $operation, $operation_arguments, $view_exposed_input, $view_arguments);
+  views_bulk_operations_execute($vid, $operation_id, $operation_arguments, $view_exposed_input, $view_arguments);
 }
diff --git a/views_bulk_operations.module b/views_bulk_operations.module
index c270b7a..f769c44 100644
--- a/views_bulk_operations.module
+++ b/views_bulk_operations.module
@@ -101,6 +101,7 @@ function views_bulk_operations_theme() {
       $themes += call_user_func($action_theme_fn);
     }
   }
+
   return $themes;
 }
 
@@ -152,6 +153,88 @@ function views_bulk_operations_get_operation_types() {
 }
 
 /**
+  * Gets the info array of an operation from the provider plugin.
+  *
+  * @param $operation_id
+  *   The id of the operation for which the info shall be returned, or NULL
+  *   to return an array with info about all operations.
+  */
+function views_bulk_operations_get_operation_info($operation_id = NULL) {
+  $operations = &drupal_static(__FUNCTION__);
+
+  if (!isset($operations)) {
+    $operations = array();
+    $plugins = views_bulk_operations_get_operation_types();
+    foreach ($plugins as $plugin) {
+      $operations += $plugin['list callback']();
+    }
+
+    uasort($operations, create_function('$a, $b', 'return strcasecmp($a["label"], $b["label"]);'));
+  }
+
+  if (!empty($operation_id)) {
+    return $operations[$operation_id];
+  }
+  else {
+    return $operations;
+  }
+}
+
+/**
+ * Returns an operation instance.
+ *
+ * @param $operation_id
+ *   The id of the operation to instantiate.
+ *   For example: action::system_message_action.
+ * @param $entity_type
+ *   The entity type on which the operation operates.
+ * @param $options
+ *   Options for this operation (label, operation settings, etc.)
+ */
+function views_bulk_operations_get_operation($operation_id, $entity_type, $options) {
+  $operations = &drupal_static(__FUNCTION__);
+
+  if (!isset($operations[$operation_id])) {
+    // Intentionally not using views_bulk_operations_get_operation_info() here
+    // since it's an expensive function that loads all the operations on the
+    // system, despite the fact that we might only need a few.
+    $id_fragments = explode('::', $operation_id);
+    $plugin = views_bulk_operations_get_operation_type($id_fragments[0]);
+    $operation_info = $plugin['list callback']($operation_id);
+
+    if ($operation_info) {
+      $operations[$operation_id] = new $plugin['handler']['class']($entity_type, $operation_info, $options);
+    }
+    else {
+      $operations[$operation_id] = FALSE;
+    }
+  }
+
+  return $operations[$operation_id];
+}
+
+/**
+ * Get all operations that match the current entity type.
+ *
+ * @param $entity_type
+ *   Entity type.
+ * @param $options
+ *   An array of options for all operations, in the form of
+ *   $operation_id => $operation_options.
+ */
+function views_bulk_operations_get_applicable_operations($entity_type, $options) {
+  $operations = array();
+  foreach (views_bulk_operations_get_operation_info() as $operation_id => $operation_info) {
+    if ($operation_info['type'] == $entity_type || $operation_info['type'] == 'entity' || $operation_info['type'] == 'system') {
+      $options[$operation_id] = !empty($options[$operation_id]) ? $options[$operation_id] : array();
+      $operations[$operation_id] = views_bulk_operations_get_operation($operation_id, $entity_type, $options[$operation_id]);
+    }
+  }
+
+  return $operations;
+}
+
+/**
  * Gets the VBO field if it exists on the passed-in view.
  *
  * @return
@@ -323,11 +406,10 @@ function views_bulk_operations_form($form, &$form_state, $vbo) {
       '#value' => TRUE,
     );
 
-    $selected_operations = array_keys($vbo->get_selected_operations());
-    $operation = $vbo->get_operation($selected_operations[0]);
+    $selected_operations = $vbo->get_selected_operations();
+    $operation = reset($selected_operations);
     if ($operation->configurable()) {
       $context = array(
-        'settings' => $operation->getAdminOption('settings', array()),
         // Pass the View along. Needed by views_send 7.x-1.x.
         // Has no performance penalty since objects are passed by reference,
         // but needing the full views object in a core action is in most cases
@@ -357,10 +439,15 @@ function views_bulk_operations_form($form, &$form_state, $vbo) {
       '#attributes' => array('class' => array('container-inline')),
     );
     if ($vbo->options['vbo']['display_type'] == 0) {
+      $options = array(0 => t('- Choose an operation -'));
+      foreach ($vbo->get_selected_operations() as $operation_id => $operation) {
+        $options[$operation_id] = $operation->label();
+      }
+
       // Create dropdown and submit button.
       $form['select']['operation'] = array(
         '#type' => 'select',
-        '#options' => array(0 => t('- Choose an operation -')) + $vbo->get_selected_operations(),
+        '#options' => $options,
       );
       $form['select']['submit'] = array(
         '#type' => 'submit',
@@ -369,11 +456,11 @@ function views_bulk_operations_form($form, &$form_state, $vbo) {
     }
     else {
       // Create buttons for operations.
-      foreach ($vbo->get_selected_operations() as $md5 => $description) {
-        $form['select'][$md5] = array(
+      foreach ($vbo->get_selected_operations() as $operation_id => $operation) {
+        $form['select'][$operation_id] = array(
           '#type' => 'submit',
-          '#value' => $description,
-          '#hash' => $md5,
+          '#value' => $operation->label(),
+          '#operation_id' => $operation_id,
         );
       }
     }
@@ -408,7 +495,6 @@ function views_bulk_operations_config_form($form, &$form_state, $view, $output)
   drupal_set_title(t('Set parameters for %operation', array('%operation' => $operation->label())), PASS_THROUGH);
 
   $context = array(
-    'settings' => $operation->getAdminOption('settings', array()),
     // Pass the View along. Needed by views_send 7.x-1.x.
     // Has no performance penalty since objects are passed by reference,
     // but needing the full views object in a core action is in most cases
@@ -541,8 +627,8 @@ function views_bulk_operations_form_validate($form, &$form_state) {
         }
       }
       else {
-        if (!empty($form_state['triggering_element']['#hash'])) {
-          $form_state['values']['operation'] = $form_state['triggering_element']['#hash'];
+        if (!empty($form_state['triggering_element']['#operation_id'])) {
+          $form_state['values']['operation'] = $form_state['triggering_element']['#operation_id'];
         }
         if (!$form_state['values']['operation']) {
           form_set_error('operation', t('No operation selected. Please select an operation to perform.'));
@@ -573,6 +659,7 @@ function views_bulk_operations_form_validate($form, &$form_state) {
  */
 function views_bulk_operations_form_submit($form, &$form_state) {
   $vbo = _views_bulk_operations_get_field($form_state['build_info']['args'][0]);
+  $entity_type = $vbo->get_entity_type();
 
   switch ($form_state['step']) {
     case 'views_form_views_form':
@@ -594,7 +681,8 @@ function views_bulk_operations_form_submit($form, &$form_state) {
         $form_state['step'] = 'views_bulk_operations_confirm_form';
       }
       else {
-        $form_state['operation'] = $operation = $vbo->get_operation($form_state['values']['operation']);
+        $options = $vbo->get_operation_options($form_state['values']['operation']);
+        $form_state['operation'] = $operation = views_bulk_operations_get_operation($form_state['values']['operation'], $entity_type, $options);
         if (!$operation->configurable() && $operation->getAdminOption('skip_confirmation')) {
           break; // Go directly to execution
         }
@@ -1062,7 +1150,7 @@ function _views_bulk_operations_entity_label($entity_type, $entity) {
 /**
  * API function to programmatically invoke a VBO.
  */
-function views_bulk_operations_execute($vid, $operation_callback, $operation_arguments = array(), $view_exposed_input = array(), $view_arguments = array()) {
+function views_bulk_operations_execute($vid, $operation_id, $operation_arguments = array(), $view_exposed_input = array(), $view_arguments = array()) {
   $view = views_get_view($vid);
   if (!is_object($view)) {
     _views_bulk_operations_report_error('Could not find view %vid.', array('%vid' => $vid));
@@ -1087,11 +1175,11 @@ function views_bulk_operations_execute($vid, $operation_callback, $operation_arg
 
   // Find the selected operation.
   $operations = $vbo->get_selected_operations();
-  if (!isset($operations[$operation_callback])) {
-    _views_bulk_operations_report_error('Could not find operation %operation in view %vid.', array('%operation' => $operation_callback, '%vid' => $vid));
+  if (!isset($operations[$operation_id])) {
+    _views_bulk_operations_report_error('Could not find operation %operation_id in view %vid.', array('%operation_id' => $operation_id, '%vid' => $vid));
     return;
   }
-  $operation = $vbo->get_operation($operation_callback);
+  $operation = views_bulk_operations_get_operation($operation_id, $vbo->get_entity_type(), $vbo->get_operation_options($operation_id));
   if ($operation_arguments) {
     $operation->formOptions = $operation_arguments;
   }
