I, like other people on here it seems, have been trying to build a multi-step form using this tutorial about how to use the Ctools wizard to do so. My ultimate goal is to replace the registration form with my own multi-step sign up. The problem I am having is that at the moment I am mostly just 'regurgitating' the information on that tutorial link, without truly understanding how the data is passed between the forms, or the manner in which it is stored.

I've read some of the comments on that page and it seems to indicate that the cache object is overwritten after passing through each step, which is no good to me.

The tutorial never really touches upon the submit process and how you access the form items you've created throughout the steps. I know what I want to do with all the information when it's collected and ready, but I think I'm still a little fuzzy on how you retrieve the information.

If anyone has any knowledge about how to use the Ctools multistep wizard so that I can understand and process the user inputs, that would be great


  • I have not tried ctools for multistep form, but had good success with building it using the drupal form api. Any reason you have to use ctools?
    – awm
    Commented Sep 20, 2013 at 2:32
  • Well, no reason as to why I have to use ctools over the regular form API, more to do with the fact that I have read that the Ctools wizard is more powerful, but there doesn't appear to be a complete set of instructions on how to construct every step of the form, which is why I think a proper answer would prove beneficial to anyone who wants to use it Commented Sep 20, 2013 at 9:39

2 Answers 2


I've just gone through the same thing myself, but without using CTools.

While I don't currently have time to write a comprehensive tutorial (which is what's really necessary, as most of the examples out there suck big time) - perhaps on the coming weekend - I can give you an overview of how I've done it. My scenario involves an existing form (for Entity Creation via the Entity Construction Kit).

First and foremost, I'm modifying the form via hook_form_alter(), but you can do the same thing in any form builder (for, say, a custom form). For simplicity, I'll refer to my implementation of hook_form_alter() as the 'form builder.'

The first thing my form builder does is determine the current 'state' of the form. I define 'state' as a single, distinct step of the form. I check for current state by looking for $form_state['current_state'] and defaulting it to a default (first step) state if not found.

It is important to understand that the data in $form_state will persist between steps of the multi-step, so when we trigger the rebuilding of the form, we can see what state we came from and act accordingly. So, we've set the $form_state['current_state'] to a default state (step1) and added the form elements we wish to display for this state into $form. (If you have other elements in $form which you don't want to show, set their #access property to false.) Now we can view the form and see our elements - great.

The next step is adding a new state and switching to it upon form submission. When the form is submitted, our submit handler looks at $form_state['current_state'] and figures out how to proceed. We can look at $form_state['triggered_element']['#value'] to figure out which button was clicked to submit the form, and act accordingly. Let's say our button in step1 was 'Next,' we can identify this click in the submit handler and do the following:

  1. Save the current form values ($form_state['values']) into $form_state['step1_values'].
  2. Change $form_state['current_state'] to be step2.
  3. Rebuild the form by setting $form_state['rebuild'] = TRUE;

When the above events happen, the form builder will be called again, and now your $form_state['current_state'] will be set to step2, so you can identify this and show different form elements. Rinse and repeat for more form steps.

The tricky part I encountered was moving 'back' to a previous state and re-populating the form with previously submitted values. For some reason I had the impression that this would work 'automagically, because Drupal,' but no dice, I had to do it manually. This is no simple task, but (luckily for you!) you can get away with a single line of code:

form_set_defaults($fields, $values, $form);

... where $fields is an array of machine names for the fields you want to populate, $values is the data you saved in $form_state['step1_values'], and $form is the current form, which is accepted by reference and modified. As for the function itself, it's available as part of the form component in my DevTools module.

Disclaimer: form_set_defaults() was only tested in my own use case of a textfield, textarea, a series of checkboxes (referencing a taxonomy), and a couple FiveStar widgets. Your mileage may vary.

This should get you most of the way there!

  • Sorry for not getting back for so long, I was on leave when you replied and forgot about it :/, I've managed to progress further with how the cache system works, and managed to separate out my cached data into individual steps. Although my method didn't directly use your answer, I was able to use the principles you outlined to get to where I needed to. I couldn't manage to make form_set_defaults() work thought :( Commented Oct 17, 2013 at 12:02

Realize this is an old thread but wanted to throw an additional explanation out there to help people.

I was able to accomplish the same with D7 by using the $form_state values as default values in the hook_form().

For example:

function brand_step_one_form($form, &$form_state) {

    $form['field_first_name'] = array(
          '#type' => 'textfield', 
          '#title' => t('First Name'), 
          '#default_value' => $form_state['step_data']['field_first_name'], 
          '#size' => 60, 
          '#maxlength' => 128, 
          '#required' => TRUE,

step_data in this instance is where I'm storing my data. Using a simple var_dump on the form_state variable will show you where yours is.

When you hit the back button, the fields will be preserved and autofilled. The only additional caveat I have is that I had to make sure that the cache wasn't overwritten when the back button was clicked.

function custom_module_next(&$form_state) {

    if ($form_state['triggering_element']['#value'] != "Back") {
        StepCache::set('data', $form_state['values']);


Lastly, make sure to set

'show back' => variable_get('step_show_back', TRUE)
. Otherwise the back button won't show.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.