diff --git form_example/form_example.info form_example/form_example.info
index c09f995..9486c58 100644
--- form_example/form_example.info
+++ form_example/form_example.info
@@ -8,4 +8,5 @@ files[] = form_example.module
 files[] = form_example.install
 files[] = form_example_tutorial.inc
 files[] = form_example_states.inc
+files[] = form_example_wizard.inc
 files[] = form_example.test
diff --git form_example/form_example.module form_example/form_example.module
index ec95d17..eeb40ad 100644
--- form_example/form_example.module
+++ form_example/form_example.module
@@ -127,24 +127,18 @@ function form_example_menu() {
     'description' => 'How to use the #states attribute in FAPI',
     'file' => 'form_example_states.inc',
   );
+  $items['examples/form_example/wizard'] = array(
+    'title' => 'Extensible wizard example',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('form_example_wizard'),
+    'access callback' => TRUE,
+    'description' => 'A general approach to a wizard multistep form.',
+    'file' => 'form_example_wizard.inc',
+  );
   return $items;
 }
 
 function form_example_intro() {
-  $markup = t('The form example module provides a tutorial and a #states example');
-  return $markup;
-}
-
-/**
- * Implements hook_help() to provide a bit of help.
- */
-function form_example_help($path, $arg) {
-  switch($path) {
-    case 'examples/form_example/tutorial':
-      // TODO: Update the URL.
-      $help = t('This form example tutorial for Drupal 7 is the code from the <a href="http://drupal.org/node/262422">Handbook 10-step tutorial</a>');
-  }
-  if (!empty($help)) {
-    return '<p>' . $help . '</p>';
-  }
+  $markup = t('The form example module provides a tutorial, extensible multistep example, and a #states example');
+  return array('#markup' => $markup);
 }
\ No newline at end of file
diff --git form_example/form_example.test form_example/form_example.test
index cfbeb1c..da900d2 100644
--- form_example/form_example.test
+++ form_example/form_example.test
@@ -3,7 +3,7 @@
 
 /**
  * @file
- * test file for form_example module.
+ * Test file for form_example module.
  */
 
 /**
@@ -14,7 +14,7 @@ class FormExampleTestCase extends DrupalWebTestCase {
   public static function getInfo() {
     return array(
       'name' => 'Form Example tests',
-      'description' => 'Various tests on the dbtng example module.' ,
+      'description' => 'Various tests on the form example module.' ,
       'group' => 'Examples',
     );
   }
@@ -106,5 +106,60 @@ class FormExampleTestCase extends DrupalWebTestCase {
       $this->assertText(t('@num: firstname @num lastname @num (@year)', array('@num' => $i, '@year' => 1950 + $i)));
     }
   }
+
+  /**
+   * Test Wizard tutorial
+   * @TODO improve this using drupal_form_submit
+   */
+  function testWizard() {
+    // Is the wizard there
+    $this->drupalGet('examples/form_example/wizard');
+    $this->assertText(t('Extensible wizard example'));
+
+    $first_name = $this->randomName(8);
+    $last_name  = $this->randomName(8);
+    $city = $this->randomName(8);
+    $aunts_name = $this->randomName(8);
+
+    // Submit the first step of the wizard
+    $options = array(
+      'first_name' => $first_name,
+      'last_name'  => $last_name,
+    );
+    $this->drupalPost('examples/form_example/wizard', $options, t('Next'));
+
+    // A label city is created, and two buttons appear, Preivous and Next
+    $this->assertText(t('Hint: Do not enter "San Francisco", and do not leave this out.'));
+
+    // Go back to the beginning and verify that the value is there.
+    $this->drupalPost(NULL, array(), t('Previous'));
+    $this->assertFieldByName('first_name', $first_name);
+    $this->assertFieldByName('last_name', $last_name);
+
+    // Go next. We should keep our values.
+    $this->drupalPost(NULL, array(), t('Next'));
+    $this->assertText(t('Hint: Do not enter "San Francisco", and do not leave this out.'));
+
+    // Try "San Francisco".
+    $this->drupalPost(NULL, array('city' => 'San Francisco'), t('Next'));
+    $this->assertText(t('You were warned not to enter "San Francisco"'));
+
+    // Try the real city.
+    $this->drupalPost(NULL, array('city' => $city), t('Next'));
+
+    // Enter the Aunt's name, but then the previous button.
+    $this->drupalPost(NULL, array('aunts_name'=> $aunts_name), t('Previous'));
+    $this->assertFieldByName('city', $city);
+
+    // Now go forward and then press finish; check for correct values.
+    $this->drupalPost(NULL, array(), t('Next'));
+    $this->drupalPost(NULL, array('aunts_name' => $aunts_name), t('Finish'));
+
+    $this->assertRaw(t('[first_name] =&gt; @first_name', array('@first_name' => $first_name)));
+    $this->assertRaw(t('[last_name] =&gt; @last_name', array('@last_name' => $last_name)));
+    $this->assertRaw(t('[city] =&gt; @city', array('@city' => $city)));
+    $this->assertRaw(t('[aunts_name] =&gt; @aunts_name', array('@aunts_name' => $aunts_name)));
+  }
+
 }
 
diff --git form_example/form_example_wizard.inc form_example/form_example_wizard.inc
new file mode 100644
index 0000000..515847a
--- /dev/null
+++ form_example/form_example_wizard.inc
@@ -0,0 +1,284 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Estensible wizard form example.
+ *
+ * This is an example of a multistep form using a wizard style. It will include
+ * the 'Previous' and 'Next' buttons when required, and a 'Finish' button at the
+ * last stage of the form submission.
+ *
+ * This example is an extensible skeleton that can include (even
+ * programatically) more steps. The demonstration form includes three steps,
+ * each step having its own validation functions.
+ *
+ * How to extend this example:
+ * - Steps are defined in the _form_example_steps() function. Include or alter
+ *   the steps as you require.
+ * - For each step, implement the corresponding 'form' function (see
+ *   'form_example_wizard_personal_info' for the first step in this example.)
+ *   Each step is a regular form, and the wizard collects all the values of the
+ *   included forms.
+ * - Optionally, you may include custom validation functions using the regular
+ *   validation hook (formname_validate). The wizard uses these validation
+ *   functions for each step.
+ * - The most important customization sttep is to change the submit handler and
+ *   do whatever you want with the collected information. In this case, the
+ *   example just shows the collected values in the various steps.
+ */
+
+/**
+ * Return the list of steps and their associated forms. This has been separated
+ * to clarify and easy the understanding of this example. You should edit this
+ * function to include the steps your wizard/multistep form requires.
+ *
+ * @return $array
+ */
+function _form_example_steps() {
+  return array(
+      1 => array(
+        'form' => 'form_example_wizard_personal_info',
+      ),
+      2 => array(
+        'form' => 'form_example_wizard_location_info',
+      ),
+      3 => array(
+        'form' => 'form_example_wizard_other_info',
+      ),
+    );
+}
+
+/**
+ * The primary formbuilder function for the wizard form. This is the form that
+ * you should call with drupal_get_form() from your code, and it will include
+ * the rest of the step forms defined. You are not required to change this
+ * function, as this will handle all the step actions for you.
+ *
+ * This form has two defined submit handlers to process the different steps:
+ *  - Previous: handles the way to get back one step in the wizard.
+ *  - Next:     handles each step form submission,
+ *
+ * The third handler, the finish button handler, is the default form _submit
+ * handler used to process the information.
+ *
+ * You are not required to change the next or previous handlers, but you must
+ * change the form_example_wizard_sbumit handler to perform the operations you
+ * need on the collected information.
+ */
+function form_example_wizard($form, &$form_state) {
+
+  // Initialize a description of the steps for the wizard.
+  if (empty($form_state['step'])) {
+    $form_state['step'] = 1;
+
+    // This array contains the function to be called at each step to get the
+    // relevant form elements. It will also store state information for each
+    // step.
+    $form_state['step_information'] = _form_example_steps();
+  }
+  $step = &$form_state['step'];
+  drupal_set_title(t('Extensible Wizard: Step @step', array('@step' => $step)));
+
+  // Call the function named in $form_state['step_information'] to get the
+  // form elements to display for this step.
+  $form = $form_state['step_information'][$step]['form']($form, $form_state);
+
+  // Show the 'previous' button if appropriate. Note that #submit is set to
+  // a special submit handler, and that we use #limit_validation_errors to
+  // skip all complaints about validation when using the back button. The
+  // values entered will be discarded, but they will not be validated, which
+  // would be annoying in a "back" button.
+  if ($step > 1) {
+    $form['prev'] = array(
+      '#type' => 'submit',
+      '#value' => t('Previous'),
+      '#name' => 'prev',
+      '#submit' => array('form_example_wizard_previous_submit'),
+      '#limit_validation_errors' => array(),
+    );
+  }
+
+  // Show the Next button only if there are more steps defined.
+  if ($step < count($form_state['step_information'])) {
+    // The Next button should be included on every step
+    $form['next'] = array(
+      '#type' => 'submit',
+      '#value' => t('Next'),
+      '#name' => 'next',
+      '#submit' => array('form_example_wizard_next_submit'),
+    );
+  }
+  else {
+    // Just in case there are no more steps, we use the default submit handler of
+    // the form wizard. Call this button Finish, Submit, or whatever you want to
+    // show. When this button is clicked, the form_example_wizard_submit handler
+    // will be called.
+    $form['finish'] = array(
+      '#type' => 'submit',
+      '#value' => t('Finish'),
+    );
+  }
+
+  // Include each validation function defined for the different steps.
+  if (function_exists($form_state['step_information'][$step]['form'] . '_validate')) {
+    $form['next']['#validate'] = array($form_state['step_information'][$step]['form'] . '_validate');
+  }
+
+  return $form;
+}
+
+/**
+ * Submit handler for the "previous" button.
+ * - Stores away $form_state['values']
+ * - Decrements the step counter
+ * - Replaces $form_state['values'] with the values from the previous state.
+ * - Forces form rebuild.
+ *
+ * You are not required to change this function.
+ */
+function form_example_wizard_previous_submit($form, &$form_state) {
+  $current_step = &$form_state['step'];
+  $form_state['step_information'][$current_step]['stored_values'] = $form_state['values'];
+  if ($current_step > 1) {
+    $current_step--;
+    $form_state['values'] = $form_state['step_information'][$current_step]['stored_values'];
+  }
+  $form_state['rebuild'] = TRUE;
+}
+
+/**
+ * Submit handler for the 'next' button.
+ * - Saves away $form_state['values']
+ * - Increments the step count.
+ * - Replace $form_state['values'] from the last time we were at this page
+ *   or with array() if we haven't been here before.
+ * - Force form rebuild.
+ *
+ * You are not required to change this function.
+ *
+ * @param unknown_type $form
+ * @param unknown_type $form_state
+ */
+function form_example_wizard_next_submit($form, &$form_state) {
+  $current_step = &$form_state['step'];
+  $form_state['step_information'][$current_step]['stored_values'] = $form_state['values'];
+
+  if ($current_step < count($form_state['step_information'])) {
+    $current_step++;
+    if (!empty($form_state['step_information'][$current_step]['stored_values'])) {
+      $form_state['values'] = $form_state['step_information'][$current_step]['stored_values'];
+    }
+    else {
+      $form_state['values'] = array();
+    }
+    $form_state['rebuild'] = TRUE;  // Force rebuild with next step.
+    return;
+  }
+}
+
+// The previous code was a 'skeleton' of a multistep wizard form. You are not
+// required to change a line on the previous code (apart from defining your own
+// steps in the _form_example_steps() function.
+//
+// All the code included from here is the content of the wizard, the steps of
+// the form.
+
+// First, let's show the defined steps for the wizard example.
+
+/**
+ * Returns form elements for the 'personal info' page of the wizard. This is the
+ * first step of the wizard, asking for two textfields: first name and last
+ * name.
+ */
+function form_example_wizard_personal_info($form, &$form_state) {
+  $form = array();
+  $form['first_name'] = array(
+    '#type' => 'textfield',
+    '#title' => t('First Name'),
+    '#default_value' => !empty($form_state['values']['first_name']) ? $form_state['values']['first_name'] : '',
+  );
+  $form['last_name'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Last Name'),
+    '#default_value' => !empty($form_state['values']['last_name']) ? $form_state['values']['last_name'] : '',
+  );
+  return $form;
+}
+
+/**
+ * Returns form elements for the 'location info' page of the wizard. This is the
+ * second step of the wizard. This step asks for a textfield value: a City. This
+ * step also includes a validation declared later.
+ */
+function form_example_wizard_location_info($form, &$form_state) {
+  $form = array();
+  $form['city'] = array(
+    '#type' => 'textfield',
+    '#title' => t('City'),
+    '#description' => t('Hint: Do not enter "San Francisco", and do not leave this out.'),
+    '#required' => TRUE,
+    '#default_value' => !empty($form_state['values']['city']) ? $form_state['values']['city'] : '',
+
+  );
+  return $form;
+}
+
+/**
+ * Custom validation form for the 'location info' page of the wizard. This is the
+ * validation function for the second step of the wizard. The city cannot be empty
+ * or be "San Francisco".
+ */
+function form_example_wizard_location_info_validate($form, &$form_state) {
+  if ($form_state['values']['city'] == 'San Francisco') {
+    form_set_error('city', t('You were warned not to enter "San Francisco"'));
+  }
+}
+
+/**
+ * Returns form elements for the 'other info' page of the wizard. This is the
+ * thid and last step of the example wizard.
+ */
+function form_example_wizard_other_info($form, &$form_state) {
+  $form = array();
+  $form['aunts_name'] = array(
+    '#type' => 'textfield',
+    '#title' => t("Your first cousin's aunt's Social Security number"),
+    '#default_value' => !empty($form_state['values']['aunts_name']) ? $form_state['values']['aunts_name'] : '',
+  );
+  return $form;
+}
+
+// And now comes the magic of the wizard, the function that should handle all the
+// inputs from the user on each different step.
+/**
+ * Wizard form submit handler.
+ * - Saves away $form_state['values']
+ * - Process all the form values.
+ *
+ * This demonstration handler just do a drupal_set_message() with the information
+ * collected on each different step of the wizard.
+ *
+ * @param unknown_type $form
+ * @param unknown_type $form_state
+ */
+function form_example_wizard_submit($form, &$form_state) {
+  $current_step = &$form_state['step'];
+  $form_state['step_information'][$current_step]['stored_values'] = $form_state['values'];
+
+  // In this case we've completed the final page of the wizard, so process the
+  // submitted information.
+  drupal_set_message(t('This information was collected by this wizard:'));
+  foreach($form_state['step_information'] as $index => $value) {
+    // Remove FAPI fields included in the values (form_token, form_id and form_build_id
+    // This is not required, you may access the values using $value['stored_values']
+    // but I'm removing them to make a more clear representation of the collected
+    // information as the complete array will be passed through drupal_set_message().
+    unset($value['stored_values']['form_id']);
+    unset($value['stored_values']['form_build_id']);
+    unset($value['stored_values']['form_token']);
+
+    // Now show all the values.
+    drupal_set_message(t('Step @num collected the following values: <pre>@result</pre>', array('@num' => $index, '@result' => print_r($value['stored_values'], TRUE))));
+  }
+}
