diff --git includes/form.inc includes/form.inc
index d34d8d0..9213c4f 100644
--- includes/form.inc
+++ includes/form.inc
@@ -1911,6 +1911,7 @@ function form_process_ahah($element) {
       'method'   => empty($element['#ahah']['method']) ? 'replace' : $element['#ahah']['method'],
       'progress' => empty($element['#ahah']['progress']) ? array('type' => 'throbber') : $element['#ahah']['progress'],
       'button'   => isset($element['#executes_submit_callback']) ? array($element['#name'] => $element['#value']) : FALSE,
+      'success_callback' => empty($element['#ahah']['success_callback']) ? 'default' : $element['#ahah']['success_callback'],
     );
 
     // Convert a simple #ahah[progress] type string into an array.
diff --git misc/ahah.js misc/ahah.js
index c0ac01a..5d9a169 100644
--- misc/ahah.js
+++ misc/ahah.js
@@ -48,6 +48,7 @@ Drupal.ahah = function(base, element_settings) {
   this.method = element_settings.method;
   this.progress = element_settings.progress;
   this.button = element_settings.button || { };
+  this.success_callback = element_settings.success_callback;
 
   if (this.effect == 'none') {
     this.showEffect = 'show';
@@ -81,11 +82,6 @@ Drupal.ahah = function(base, element_settings) {
       return ahah.beforeSubmit(form_values, element_settings, options);
     },
     success: function(response, status) {
-      // Sanity check for browser support (object expected).
-      // When using iFrame uploads, responses must be returned as a string.
-      if (typeof(response) == 'string') {
-        response = Drupal.parseJson(response);
-      }
       return ahah.success(response, status);
     },
     complete: function(response, status) {
@@ -149,12 +145,13 @@ Drupal.ahah.prototype.beforeSubmit = function (form_values, element, options) {
  * Handler for the form redirection completion.
  */
 Drupal.ahah.prototype.success = function (response, status) {
-  var wrapper = $(this.wrapper);
-  var form = $(this.element).parents('form');
-  // Manually insert HTML into the jQuery object, using $() directly crashes
-  // Safari with long string lengths. http://dev.jquery.com/ticket/1152
-  var new_content = $('<div></div>').html(response.data);
+  // Sanity check for browser support (object expected).
+  // When using iFrame uploads, responses must be returned as a string.
+  if (typeof(response) == 'string') {
+    response = Drupal.parseJson(response);
+  }
 
+  var form = $(this.element).parents('form');
   // Restore the previous action and target to the form.
   form.attr('action', this.form_action);
   this.form_target ? form.attr('target', this.form_target) : form.removeAttr('target');
@@ -169,38 +166,64 @@ Drupal.ahah.prototype.success = function (response, status) {
   }
   $(this.element).removeClass('progress-disabled').attr('disabled', false);
 
-  // Add the new content to the page.
-  Drupal.freezeHeight();
-  if (this.method == 'replace') {
-    wrapper.empty().append(new_content);
-  }
-  else {
-    wrapper[this.method](new_content);
-  }
+  // Call the success callback.
+  this.successCallbacks[this.success_callback](this, response, status);
+}
 
-  // Immediately hide the new content if we're using any effects.
-  if (this.showEffect != 'show') {
-    new_content.hide();
-  }
+/**
+ * Container for AHAH success callback.
+ *
+ * New success callbacks can be added by defining a new function in the
+ * Drupal.ahah.prototype.successCallbacks namespace, and can then be set
+ * as 'success_callback' in an element's #ahah properties.
+ */
+Drupal.ahah.prototype.successCallbacks = {
+  /**
+   * The default AHAH success callback.
+   *
+   * Inserts the new content into the document using the method and wrapper
+   * specified in the element properties.
+   */
+  default: function (element, response, status) {
+    var wrapper = $(element.wrapper);
+
+    // Manually insert HTML into the jQuery object, using $() directly crashes
+    // Safari with long string lengths. http://dev.jquery.com/ticket/1152
+    var new_content = $('<div></div>').html(response.data);
+
+    // Add the new content to the page.
+    Drupal.freezeHeight();
+    if (element.method == 'replace') {
+      wrapper.empty().append(new_content);
+    }
+    else {
+      wrapper[element.method](new_content);
+    }
 
-  // Determine what effect use and what content will receive the effect, then
-  // show the new content.
-  if ($('.ahah-new-content', new_content).size() > 0) {
-    $('.ahah-new-content', new_content).hide();
-    new_content.show();
-    $(".ahah-new-content", new_content)[this.showEffect](this.showSpeed);
-  }
-  else if (this.showEffect != 'show') {
-    new_content[this.showEffect](this.showSpeed);
-  }
+    // Immediately hide the new content if we're using any effects.
+    if (element.showEffect != 'show') {
+      new_content.hide();
+    }
 
-  // Attach all javascript behaviors to the new content, if it was successfully
-  // added to the page, this if statement allows #ahah[wrapper] to be optional.
-  if (new_content.parents('html').length > 0) {
-    Drupal.attachBehaviors(new_content);
-  }
+    // Determine what effect use and what content will receive the effect, then
+    // show the new content.
+    if ($('.ahah-new-content', new_content).size() > 0) {
+      $('.ahah-new-content', new_content).hide();
+      new_content.show();
+      $(".ahah-new-content", new_content)[element.showEffect](element.showSpeed);
+    }
+    else if (element.showEffect != 'show') {
+      new_content[element.showEffect](element.showSpeed);
+    }
 
-  Drupal.unfreezeHeight();
+    // Attach all javascript behaviors to the new content, if it was successfully
+    // added to the page, element if statement allows #ahah[wrapper] to be optional.
+    if (new_content.parents('html').length > 0) {
+      Drupal.attachBehaviors(new_content);
+    }
+
+    Drupal.unfreezeHeight();
+  }
 };
 
 /**
