diff --git a/uc_attribute/uc_attribute.admin.inc b/uc_attribute/uc_attribute.admin.inc
index 7ac73e5..015b13c 100644
--- a/uc_attribute/uc_attribute.admin.inc
+++ b/uc_attribute/uc_attribute.admin.inc
@@ -191,6 +191,120 @@ function uc_attribute_delete_confirm_submit($form, &$form_state) {
 }
 
 /**
+ * Form builder for bulk product updates.
+ *
+ * @see uc_attribute_bulk_update_flush()
+ * @see uc_attribute_bulk_update_flush_all()
+ * @see theme_uc_attribute_bulk_update_form()
+ * @ingroup forms
+ */
+function uc_attribute_bulk_update_form($form_state, $class) {
+  drupal_set_title(check_plain($class->name));
+
+  $form['node_type'] = array(
+    '#type' => 'value',
+    '#value' => $class->pcid,
+  );
+
+  // Select all products with current class.
+  $result = pager_query("SELECT n.nid, n.title, n.status, nt.name FROM {node} n LEFT JOIN {node_type} nt ON nt.type = n.type WHERE nt.type = '%s'", 50, 0, NULL, $class->pcid);
+  $nodes = array();
+  while ($node = db_fetch_object($result)) {
+    $nodes[$node->nid] = '';
+    $form['title'][$node->nid] = array('#value' => l($node->title, 'node/' . $node->nid));
+    $form['status'][$node->nid] =  array('#value' => ($node->status ? t('published') : t('not published')));
+    $form['type'][$node->nid] =  array('#value' => check_plain($node->name));
+  }
+
+  $form['nodes'] = array('#type' => 'checkboxes', '#options' => $nodes);
+
+  $form['flush'] = array(
+    '#type' => 'submit',
+    '#value' => t('Flush selected'),
+    '#submit' => array('uc_attribute_bulk_update_flush'),
+  );
+  $form['flush_all'] = array(
+    '#type' => 'submit',
+    '#value' => t('Flush all'),
+    '#submit' => array('uc_attribute_bulk_update_flush_all'),
+  );
+
+  $form['pager'] = array('#value' => theme('pager', NULL, 50, 0));
+
+  return $form;
+}
+
+/**
+ * Displays the bulk product update form.
+ *
+ * @see uc_attribute_bulk_update_form()
+ * @ingroup themeable
+ */
+function theme_uc_attribute_bulk_update_form($form) {
+  $output = '';
+
+  $has_products = isset($form['title']) && is_array($form['title']);
+
+  $header[] = theme('table_select_header_cell');
+  $header[] = t('Title');
+  $header[] = t('Status');
+  $header[] = t('Type');
+
+  if ($has_products) {
+    foreach (element_children($form['title']) as $key) {
+      $row = array();
+      $row[] = drupal_render($form['nodes'][$key]);
+      $row[] = drupal_render($form['title'][$key]);
+      $row[] = drupal_render($form['status'][$key]);
+      $row[] = drupal_render($form['type'][$key]);
+      $rows[] = $row;
+    }
+  }
+  else {
+    $rows[] = array(array('data' => t('No products available.'), 'colspan' => '6'));
+  }
+
+  $output .= theme('table', $header, $rows);
+  if ($form['pager']['#value']) {
+    $output .= drupal_render($form['pager']);
+  }
+
+  $output .= drupal_render($form);
+
+  $output .= '<p>' . t('Flush actions take place immediately and cannot be undone.') . '</p>';
+
+  return $output;
+}
+
+/**
+ * Form submission handler for uc_attribute_bulk_update_form().
+ *
+ * @see uc_attribute_bulk_update_form()
+ */
+function uc_attribute_bulk_update_flush($form, &$form_state) {
+  $nodes = array_filter($form_state['values']['nodes']);
+  foreach ($nodes as $nid) {
+    $node = node_load($nid);
+    uc_attribute_node_reset($node);
+  }
+  drupal_set_message(t("Bulk update completed successfully on selected products."));
+}
+
+/**
+ * Form submission handler for uc_attribute_bulk_update_form().
+ *
+ * @see uc_attribute_bulk_update_form()
+ */
+function uc_attribute_bulk_update_flush_all($form, &$form_state) {
+  $result = db_query("SELECT nid FROM {node} WHERE type = '%s'", $form_state['values']['node_type']);
+  while ($item = db_fetch_object($result)) {
+    $node = node_load($item->nid);
+    uc_attribute_node_reset($node);
+  }
+  drupal_set_message(t("Bulk update completed successfully on all products in this class."));
+}
+
+/**
  * Changes the display of attribute option prices.
  *
  * @ingroup forms
@@ -419,7 +533,15 @@ function uc_attribute_option_form($form_state, $attribute, $option = NULL) {
     '#default_value' => $option->weight,
     '#weight' => 3,
   );
-
+  if (isset($form['oid'])) {
+    $form['bulk_update'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Update existing products?'),
+      '#description' => t('If selected, existing products and product classes with this attribute will be updated with the values on this page.<br /><em><strong>Warning:</strong> any option adjustments set at the product level will be overridden!</em>'),
+      '#default_value' => 0,
+      '#weight' => 4,
+    );
+  }
   $form['submit'] = array(
     '#type' => 'submit',
     '#value' => t('Submit'),
@@ -469,7 +591,17 @@ function uc_attribute_option_form_submit($form, &$form_state) {
     db_query("UPDATE {uc_attribute_options} SET name = '%s', cost = %f, price = %f, weight = %f, ordering = %d WHERE aid = %d AND oid = %d",
       $form_state['values']['name'], $form_state['values']['cost'], $form_state['values']['price'], $form_state['values']['weight'], $form_state['values']['ordering'], $form_state['values']['aid'], $form_state['values']['oid']);
     drupal_set_message(t('Updated option %option.', array('%option' => $form_state['values']['name'])));
-    watchdog('uc_attribute', 'Updated option %option.', array('%option' => $form_state['values']['name']), WATCHDOG_NOTICE, 'admin/store/attributes/'. $form_state['values']['aid'] .'/options/'. $form_state['values']['oid']);
+    if ($form_state['values']['bulk_update']) {
+      db_query("UPDATE {uc_product_options} SET cost = '%f', price = '%f', weight = '%f', ordering = '%d' WHERE oid = '%d'",
+        $form_state['values']['cost'], $form_state['values']['price'], $form_state['values']['weight'], $form_state['values']['ordering'], $form_state['values']['oid']);
+      db_query("UPDATE {uc_class_attribute_options} SET cost = '%f', price = '%f', weight = '%f', ordering = '%d' WHERE oid = '%d'",
+        $form_state['values']['cost'], $form_state['values']['price'], $form_state['values']['weight'], $form_state['values']['ordering'], $form_state['values']['oid']);
+      drupal_set_message(t('Bulk updates applied to all products and classes which have this attribute.'));
+      watchdog('uc_attribute', 'Updated option %option for all products and classes.', array('%option' => $form_state['values']['name']), WATCHDOG_NOTICE, 'admin/store/attributes/'. $form_state['values']['aid'] .'/options/'. $form_state['values']['oid']);
+    }
+    else {
+      watchdog('uc_attribute', 'Updated option %option.', array('%option' => $form_state['values']['name']), WATCHDOG_NOTICE, 'admin/store/attributes/'. $form_state['values']['aid'] .'/options/'. $form_state['values']['oid']);
+    }
     $form_state['redirect'] = 'admin/store/attributes/'. $form_state['values']['aid'] .'/options';
   }
 }
@@ -517,6 +649,7 @@ function uc_attribute_option_delete_confirm_submit($form, &$form_state) {
  * Form to associate attributes with products or classes.
  *
  * @see uc_object_attributes_form_submit()
+ * @see uc_object_attributes_form_reset()
  * @see theme_uc_object_attributes_form()
  * @ingroup forms
  */
@@ -604,6 +737,14 @@ function uc_object_attributes_form($form_state, $object, $type, $view = 'overvie
         '#weight' => -2,
       );
     }
+    if ($type == 'product') {
+      $form['reset'] = array(
+        '#type' => 'submit',
+        '#value' => t('Reset to defaults'),
+        '#submit' => array('uc_object_attributes_form_reset'),
+        '#weight' => -1,
+      );
+    }
   }
   elseif ($view == 'add') {
     // Get list of attributes not already assigned to this node or class.
@@ -781,6 +922,50 @@ function uc_object_attributes_form_submit($form, &$form_state) {
 }
 
 /**
+ * Reset product attributes to defaults for product class.
+ *
+ * @see uc_object_attributes_form_reset()
+ */
+function uc_object_attributes_form_reset($form, &$form_state) {
+  $form_state['redirect'] = array('node/'. $form_state['values']['id'] .'/edit/attributes/reset');
+}
+
+/**
+ * Confirmation form to reset product attributes to defaults.
+ *
+ * @see uc_object_attributes_form()
+ * @see uc_attribute_node_reset_confirm_submit()
+ */
+function uc_attribute_node_reset_confirm(&$form_state, $node) {
+  $form['nid'] = array(
+    '#type' => 'value',
+    '#value' => $node->nid,
+  );
+
+  return confirm_form($form,
+    t('Are you sure you want to reset the product attributes and options for %title?', array('%title' => $node->title)),
+    'node/'. $node->nid .'/edit/attributes',
+    t('This action cannot be undone.'),
+    t('Reset'),
+    t('Cancel')
+  );
+}
+
+/**
+ * Execute product attribute reset.
+ *
+ * @see uc_attribute_node_reset_confirm()
+ */
+function uc_attribute_node_reset_confirm_submit($form, &$form_state) {
+  $nid = $form_state['values']['nid'];
+  $node = node_load($nid);
+  uc_attribute_node_reset($node);
+  drupal_set_message(t('Product attributes and options reset to class defaults.'));
+
+  $form_state['redirect'] = 'node/'. $node->nid .'/edit/attributes';
+}
+
+/**
  * Form to assign and modify attribute options on products or classes.
  *
  * @see uc_object_options_form_validate()
diff --git a/uc_attribute/uc_attribute.module b/uc_attribute/uc_attribute.module
index 1da3846..f79e79a 100644
--- a/uc_attribute/uc_attribute.module
+++ b/uc_attribute/uc_attribute.module
@@ -42,6 +42,10 @@ function uc_attribute_help($path, $arg) {
     // Help message for the product Adjustments tab.
     case 'node/%/edit/adjustments':
       return t('Enter an alternate SKU to be used when the specified set of options are chosen and the product is added to the cart. <b>Warning:</b> Adding or removing attributes from this product will reset all the SKUs on this page to the default product SKU.');
+
+    // Help message for bulk updates tab.
+    case 'admin/store/products/classes/%/attributes_bulk':
+      return t('Bulk updates performed here will reset all attributes and options on the selected products to the current defaults for this product class.<p><em><strong>Warning:</strong> any product level customizations will be lost!</em></p>To perform bulk updates on specific option values without resetting attributes to the class defaults, go to <a href="!url">Store administration->Attributes</a> and edit the option, selecting the checkbox to update existing products.', array('!url' => url('admin/store/attributes')));
   }
 }
 
@@ -155,6 +159,15 @@ function uc_attribute_menu() {
     'weight' => 2,
     'file' => 'uc_attribute.admin.inc',
   );
+  $items['admin/store/products/classes/%uc_product_class/attributes_bulk'] = array(
+    'title' => 'Bulk',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('uc_attribute_bulk_update_form', 4, 'class'),
+    'access callback' => 'uc_attribute_product_class_access',
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 3,
+    'file' => 'uc_attribute.admin.inc',
+  );
 
   // Insert subitems into the edit node page for product types.
   $items['node/%node/edit/attributes'] = array(
@@ -177,6 +190,15 @@ function uc_attribute_menu() {
     'weight' => 1,
     'file' => 'uc_attribute.admin.inc',
   );
+  $items['node/%node/edit/attributes/reset'] = array(
+    'title' => 'Reset to defaults',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('uc_attribute_node_reset_confirm', 1),
+    'access callback' => 'uc_attribute_product_access',
+    'access arguments' => array(1),
+    'type' => MENU_CALLBACK,
+    'file' => 'uc_attribute.admin.inc',
+  );
   $items['node/%node/edit/options'] = array(
     'title' => 'Options',
     'page callback' => 'drupal_get_form',
@@ -271,6 +293,10 @@ function uc_attribute_theme() {
       'arguments' => array('product' => NULL),
       'file' => 'uc_attribute.admin.inc',
     ),
+    'uc_attribute_bulk_update_form' => array(
+      'arguments' => array('form' => NULL),
+      'file' => 'uc_attribute.admin.inc',
+    ),
   );
 }
 
@@ -349,23 +375,10 @@ function uc_attribute_nodeapi(&$node, $op, $arg3 = NULL, $arg4 = NULL) {
         }
         break;
       case 'insert':
-        switch ($GLOBALS['db_type']) {
-          case 'mysqli':
-          case 'mysql':
-            db_query("INSERT IGNORE INTO {uc_product_attributes} (nid, aid, label, ordering, required, display, default_option) SELECT %d, aid, label, ordering, required, display, default_option FROM {uc_class_attributes} WHERE pcid = '%s'", $node->nid, $node->type);
-            db_query("INSERT IGNORE INTO {uc_product_options} (nid, oid, cost, price, weight, ordering) SELECT %d, oid, cost, price, weight, ordering FROM {uc_class_attribute_options} WHERE pcid = '%s'", $node->nid, $node->type);
-            break;
-          case 'pgsql':
-            db_query("INSERT INTO {uc_product_attributes} (nid, aid, label, ordering, required, display, default_option) SELECT %d, aid, label, ordering, required, display, default_option FROM {uc_class_attributes} WHERE pcid = '%s'", $node->nid, $node->type);
-            db_query("INSERT INTO {uc_product_options} (nid, oid, cost, price, weight, ordering) SELECT %d, oid, cost, price, weight, ordering FROM {uc_class_attribute_options} WHERE pcid = '%s'", $node->nid, $node->type);
-            break;
-        }
-
+        uc_attribute_node_insert($node);
         break;
       case 'delete':
-        db_query("DELETE FROM {uc_product_options} WHERE nid = %d", $node->nid);
-        db_query("DELETE FROM {uc_product_adjustments} WHERE nid = %d", $node->nid);
-        db_query("DELETE FROM {uc_product_attributes} WHERE nid = %d", $node->nid);
+        uc_attribute_node_delete($node);
         break;
       case 'update index':
         $output = '';
@@ -386,6 +399,40 @@ function uc_attribute_nodeapi(&$node, $op, $arg3 = NULL, $arg4 = NULL) {
   }
 }
 
+/**
+ * Add default attributes to a product node.
+ */
+function uc_attribute_node_insert($node) {
+  switch ($GLOBALS['db_type']) {
+    case 'mysqli':
+    case 'mysql':
+      db_query("INSERT IGNORE INTO {uc_product_attributes} (nid, aid, label, ordering, required, display, default_option) SELECT %d, aid, label, ordering, required, display, default_option FROM {uc_class_attributes} WHERE pcid = '%s'", $node->nid, $node->type);
+      db_query("INSERT IGNORE INTO {uc_product_options} (nid, oid, cost, price, weight, ordering) SELECT %d, oid, cost, price, weight, ordering FROM {uc_class_attribute_options} WHERE pcid = '%s'", $node->nid, $node->type);
+      break;
+    case 'pgsql':
+      db_query("INSERT INTO {uc_product_attributes} (nid, aid, label, ordering, required, display, default_option) SELECT %d, aid, label, ordering, required, display, default_option FROM {uc_class_attributes} WHERE pcid = '%s'", $node->nid, $node->type);
+      db_query("INSERT INTO {uc_product_options} (nid, oid, cost, price, weight, ordering) SELECT %d, oid, cost, price, weight, ordering FROM {uc_class_attribute_options} WHERE pcid = '%s'", $node->nid, $node->type);
+      break;
+  }
+}
+
+/**
+ * Remove all attributes from a product node.
+ */
+function uc_attribute_node_delete($node) {
+  db_query("DELETE FROM {uc_product_options} WHERE nid = %d", $node->nid);
+  db_query("DELETE FROM {uc_product_adjustments} WHERE nid = %d", $node->nid);
+  db_query("DELETE FROM {uc_product_attributes} WHERE nid = %d", $node->nid);
+}
+
+/**
+ * Reset attributes to class defaults for a product node.
+ */
+function uc_attribute_node_reset($node) {
+  uc_attribute_node_delete($node);
+  uc_attribute_node_insert($node);
+}
+
 /******************************************************************************
  * Ubercart Hooks                                                             *
  ******************************************************************************/
diff --git a/uc_attribute/uc_attribute.test b/uc_attribute/uc_attribute.test
index ad705b0..975c180 100644
--- a/uc_attribute/uc_attribute.test
+++ b/uc_attribute/uc_attribute.test
@@ -25,7 +25,7 @@ class UbercartAttributeTestCase extends UbercartTestHelper {
    * Overrides DrupalWebTestCase::setUp().
    */
   function setUp() {
-    parent::setUp(array('uc_attribute'), array('administer attributes'));
+    parent::setUp(array('uc_attribute'), array('administer attributes', 'administer nodes'));
     $this->drupalLogin($this->adminUser);
   }
 
@@ -538,6 +538,57 @@ class UbercartAttributeTestCase extends UbercartTestHelper {
   }
 
   /**
+   * Tests the "bulk edit attribute options" user interface.
+   */
+  public function testAttributeUIAttributeOptionsBulkEdit() {
+    $attribute = self::createAttribute();
+    $option = self::createAttributeOption(array('aid' => $attribute->aid));
+    uc_attribute_option_save($option);
+
+    $class = $this->createProductClass();
+    uc_attribute_subject_save($attribute, 'class', $class->pcid);
+    uc_attribute_subject_option_save($option, 'class', $class->pcid);
+
+    $product = $this->createProduct();
+    uc_attribute_subject_save($attribute, 'product', $product->nid);
+    uc_attribute_subject_option_save($option, 'product', $product->nid);
+
+    $edit = (array) self::createAttributeOption(array('aid' => $attribute->aid), FALSE);
+    unset($edit['aid']);
+    $edit['bulk_update'] = TRUE;
+    $this->drupalPost('admin/store/attributes/'. $attribute->aid .'/options/'. $option->oid .'/edit', $edit, t('Submit'));
+    unset($edit['bulk_update']);
+
+    $this->assertText('Bulk updates applied', t('Bulk update selected.'), t('Ubercart'));
+
+    $option = uc_attribute_subject_option_load($option->oid, 'class', $class->pcid);
+    $fields_ok = TRUE;
+    foreach ($edit as $field => $value) {
+      if ($option->$field != $value) {
+        $this->showVar($option);
+        $this->showVar($edit);
+        $fields_ok = FALSE;
+        break;
+      }
+    }
+
+    $this->assertTrue($fields_ok, t('Class bulk updated successfully.'), t('Ubercart'));
+
+    $product_attributes = uc_attribute_load_multiple(array(), 'product', $product->nid);
+    $fields_ok = TRUE;
+    foreach ($edit as $field => $value) {
+      if ($product_attributes[$attribute->aid]->options[$option->oid]->$field != $value) {
+        $this->showVar($product_attributes);
+        $this->showVar($edit);
+        $fields_ok = FALSE;
+        break;
+      }
+    }
+
+    $this->assertTrue($fields_ok, t('Product bulk updated successfully.'), t('Ubercart'));
+  }
+
+  /**
    * Tests the "delete attribute option" user interface.
    */
   public function testAttributeUIAttributeOptionsDelete() {
@@ -663,6 +714,58 @@ class UbercartAttributeTestCase extends UbercartTestHelper {
   }
 
   /**
+   * Tests the product node attribute user interface.
+   */
+  public function testAttributeUIProductAttributeOverview() {
+    $class = $this->createProductClass();
+    $attribute = self::createAttribute();
+    uc_attribute_subject_save($attribute, 'class', $class->pcid);
+    $product = $this->createProduct(array('type' => $class->pcid));
+
+    // Check product has attribute added by default.
+    $loaded_attribute = uc_attribute_load($attribute->aid, $product->nid, 'product');
+    $this->assertEqual($attribute->name, $loaded_attribute->name, t('Product attribute added by default.'), t('Ubercart'));
+    uc_attribute_subject_delete($attribute->aid, 'product', $product->nid);
+
+    $this->drupalGet('node/'. $product->nid .'/edit/attributes/add');
+    $edit = array();
+    $edit['add_attributes[]'] = $attribute->aid;
+    $this->drupalPost(NULL, $edit, t('Add attributes'));
+    $this->assertText($attribute->name, t('Product attribute added.'), t('Ubercart'));
+
+    $edit = array();
+    $edit["attributes[{$attribute->aid}][remove]"] = TRUE;
+    $this->drupalPost(NULL, $edit, t('Save changes'));
+    $this->assertText(t('You must first add attributes to this product.'), t('Product attribute removed.'), t('Ubercart'));
+
+    $this->drupalPost(NULL, array(), t('Reset to defaults'));
+    $this->drupalPost(NULL, array(), t('Reset'));
+    $this->assertText($attribute->name, t('Product attribute reset successfully.'), t('Ubercart'));
+  }
+
+  /**
+   * Tests the bulk product attribute update user interface.
+   */
+  public function testAttributeUIProductAttributeBulkUpdate() {
+    $class = $this->createProductClass();
+    $attribute = self::createAttribute();
+    uc_attribute_subject_save($attribute, 'class', $class->pcid);
+    $product = $this->createProduct(array('type' => $class->pcid));
+
+    $edit = array();
+    $edit["attributes[{$attribute->aid}][remove]"] = TRUE;
+    $this->drupalPost('node/'. $product->nid .'/edit/attributes', $edit, t('Save changes'));
+    $this->assertText(t('You must first add attributes to this product.'), t('Product attribute removed.'), t('Ubercart'));
+
+    $this->drupalGet('admin/store/products/classes/'. $class->pcid .'/attributes_bulk');
+    $this->assertText($product->title, t('Product name found.'), t('Ubercart'));
+    $this->drupalPost(NULL, array(), t('Flush all'));
+
+    $this->drupalGet('node/'. $product->nid .'/edit/attributes');
+    $this->assertText($attribute->name, t('Product attribute reset successfully.'), t('Ubercart'));
+  }
+
+  /**
    * Creates a product adjustment SKU.
    *
    * @param $data
diff --git a/uc_store/uc_store.test b/uc_store/uc_store.test
index a567b97..cd3c385 100644
--- a/uc_store/uc_store.test
+++ b/uc_store/uc_store.test
@@ -95,6 +95,8 @@ class UbercartTestHelper extends DrupalWebTestCase {
     $product_class = (object) $product_class;
 
     drupal_write_record('uc_product_classes', $product_class);
+    uc_product_node_info(TRUE);
+    node_types_rebuild();
 
     return $product_class;
   }
