diff --git a/nodeaccess.module b/nodeaccess.module
index 781bac8..bd42b85 100644
--- a/nodeaccess.module
+++ b/nodeaccess.module
@@ -902,6 +902,11 @@ function theme_nodeaccess_grants_form($variables) {
   // @todo: Number of parameters in this theme function does not match number
   // of parameters found in hook_theme.
   $form = $variables['form'];
+
+  if (!empty($form['add_or_remove'])) {
+   $output .= drupal_render($form['add_or_remove']);
+  }
+
   $rows = array();
   $allowed_roles = variable_get('nodeaccess-roles', array());
   $allowed_grants = variable_get('nodeaccess-grants', array());
@@ -950,12 +955,14 @@ function theme_nodeaccess_grants_form($variables) {
   }
 
   // Search form.
-  $output .= '<p><div class="search-form">';
-  $output .= '<strong>' . t('Enter names to search for users:') . '</strong>';
-  $output .= '<div class="container-inline">';
-  $output .= drupal_render($form['keys']);
-  $output .= drupal_render($form['search']);
-  $output .= '</div></div></p>';
+  if (!empty($form['keys'])) {
+    $output .= '<p><div class="search-form">';
+    $output .= '<strong>' . t('Enter names to search for users:') . '</strong>';
+    $output .= '<div class="container-inline">';
+    $output .= drupal_render($form['keys']);
+    $output .= drupal_render($form['search']);
+    $output .= '</div></div></p>';
+  }
 
   // Users table.
   unset($rows);
@@ -1346,3 +1353,193 @@ function _nodeaccess_get_grants($node) {
   }
   return $grants;
 }
+
+
+/**
+ * Implements hook_action_info().
+ */
+function nodeaccess_action_info() {
+  return array(
+    'nodeaccess_bulk_update' => array(
+      'type' => 'node',
+      'label' => t('Update node access grants'),
+      'configurable' => TRUE,
+      'vbo_configurable' => FALSE,
+      'pass rows' => TRUE,
+      'permissions' => array('grant node permissions'),
+    ),
+  );
+}
+
+/**
+ * Form to add or remove bulk grants.
+ */
+function nodeaccess_bulk_update_form($settings, &$form_state) {
+  $form = array();
+
+  // Use theme function for per node editing.
+  $form['#theme'] = 'nodeaccess_grants_form';
+
+  $form['add_or_remove'] = array(
+    '#type' => 'radios',
+    '#title' => t("Do you want to add or remove the following grants?"),
+    '#required' => TRUE,
+    '#options' => array(
+      'add' => t("Add"),
+      'remove' => t("Remove"),
+    ),
+  );
+
+  $allowed_roles = variable_get('nodeaccess-roles', array());
+  $allowed_grants = variable_get('nodeaccess-grants', array());
+
+  $roles = user_roles();
+
+  // Roles table.
+  if (is_array($roles)) {
+    $form['rid'] = array('#tree' => TRUE);
+    foreach ($roles as $key => $value) {
+      if (isset($allowed_roles[$key]) && $allowed_roles[$key]) {
+        $form['rid'][$key]['name'] = array(
+          '#type' => 'hidden',
+          '#value' => $value,
+        );
+        if ($allowed_grants['view']) {
+          $form['rid'][$key]['grant_view'] = array(
+            '#type' => 'checkbox',
+          );
+        }
+        if ($allowed_grants['edit']) {
+          $form['rid'][$key]['grant_update'] = array(
+            '#type' => 'checkbox',
+          );
+        }
+        if ($allowed_grants['delete']) {
+          $form['rid'][$key]['grant_delete'] = array(
+            '#type' => 'checkbox',
+          );
+        }
+      }
+    }
+  }
+
+  // @todo add processing for user based access
+
+  return $form;
+}
+
+/**
+ * Submit handler for nodeaccess_bulk_update_form().
+ */
+function nodeaccess_bulk_update_submit($form, &$form_state) {
+
+  return $form_state;
+}
+
+/**
+ * Action to update node grants on bulk.
+ */
+function nodeaccess_bulk_update(&$node, $context) {
+
+  $method = $context['values']['add_or_remove'];
+
+  $new_grants = array();
+
+  $original_grants = array();
+
+  // Load original granted roles.
+  $result = db_query("SELECT r.rid, nra.name, na.grant_view, na.grant_update, na.grant_delete
+      FROM {role} r
+      LEFT JOIN {nodeaccess_role_alias} nra ON r.rid = nra.rid
+      LEFT JOIN {node_access} na ON r.rid = na.gid AND na.realm = :realm AND na.nid = :nid
+      ORDER BY nra.weight, nra.name", array(':realm' => 'nodeaccess_rid', ':nid' => $node->nid));
+  foreach ($result as $grant) {
+    $original_grants['rid'][$grant->rid] = array(
+      'name' => $grant->name,
+      'grant_view' => (boolean) $grant->grant_view,
+      'grant_update' => (boolean) $grant->grant_update,
+      'grant_delete' => (boolean) $grant->grant_delete,
+    );
+  }
+
+  // Start empty array if original node doesn't have rid based grants.
+  $original['rid'] = isset($original_grants['rid']) ? $original_grants['rid'] : array();
+
+  // Start by setting new grants from submitted form values.
+  $new_grants['rid'] = $context['values']['rid'];
+
+  // Iterate original grants and ensure they're kept
+  // in the new grants to be set
+  foreach ($original_grants['rid'] as $rid => $values) {
+    foreach ($values as $key => $value) {
+      if (strpos($key, 'grant_') !== FALSE && $value == 1) {
+        // Preserve existing grant value
+        $new_grants['rid'][$rid][$key] = 1;
+      }
+    }
+  }
+
+  // Remove option needs to flip the values to zero when grant is checked.
+  if ($method == 'remove') {
+
+    foreach ($new_grants['rid'] as $rid => $values) {
+      foreach ($values as $key => $value) {
+        if (strpos($key, 'grant_') !== FALSE) {
+          // Check if the form item was checked for this role and grant.
+          if ($context['values']['rid'][$rid][$key] == 1) {
+            // In the remove method, selected form values
+            // mean remove- so set to 0.
+            $new_grants['rid'][$rid][$key] = 0;
+          }
+        }
+      }
+    }
+
+  }
+
+  // @todo add processing for user based access
+
+  // Prepare grants array ready for node_access_write_grants() processing.
+  $grants = array();
+
+  foreach (array('uid', 'rid') as $type) {
+    $realm = 'nodeaccess_' . $type;
+
+    if (isset($new_grants[$type]) && is_array($new_grants[$type])) {
+      foreach ($new_grants[$type] as $gid => $line) {
+        $grant = array(
+          'gid' => $gid,
+          'realm' => $realm,
+          'grant_view' => empty($line['grant_view']) ? 0 : 1,
+          'grant_update' => empty($line['grant_update']) ? 0 : 1,
+          'grant_delete' => empty($line['grant_delete']) ? 0 : 1,
+        );
+
+        if ($grant['grant_view'] || $grant['grant_update'] || $grant['grant_delete']) {
+          $grants[] = $grant;
+        }
+
+      }
+      node_access_write_grants($node, $grants, $realm);
+    }
+
+  }
+
+  // Save role and user grants to our own table.
+  db_delete('nodeaccess')
+    ->condition('nid', $node->nid)
+    ->execute();
+  foreach ($grants as $grant) {
+    db_insert('nodeaccess')
+      ->fields(array(
+        'nid' => $node->nid,
+        'gid' => $grant['gid'],
+        'realm' => $grant['realm'],
+        'grant_view' => $grant['grant_view'],
+        'grant_update' => $grant['grant_update'],
+        'grant_delete' => $grant['grant_delete'],
+      ))
+      ->execute();
+  }
+
+}
\ No newline at end of file
