I recently did some work for a client that involved multi-page forms. Specifically, the client wanted a page where users could choose one of five forms, each of which had about ten steps with one question each. Users had to be able to move forward and backwards through the forms, with their data saved when they finished.
I knew a lot about working with forms in Drupal, although I'd never actually worked that extensively with multi-paged forms. But I did my research, and luckily there are some nice tutorials out there. However, most of these were specifically for forms with only two pages, or covered older versions of Drupal, or were crazy complicated. There is a great tutorial at pingVision which I unfortunately found after the fact, but this also doesn't cover a "back" button.
So, here's an example of what I came up with. It's a little clumsy, and there's probably room for simplification, but it works. You can move back and forward in the form and all of your data stays inputted without getting mysteriously wiped away.
<?php
/**
* This is our form callback.
* Display using drupal_get_form('work_survey');
*/
function work_survey(&$form_state) {
// $form_state['storage']['step'] keeps track of what page we're on.
if (!isset($form_state['storage']['step'])) {
$form_state['storage']['step'] = 1;
}
//Don't lose our old data when returning to a page with data already typed in.
$default_value = '';
if (isset($form_state['storage']['values'][$form_state['storage']['step']])) {
$default_value = $form_state['storage']['values'][$form_state['storage']['step']];
}
switch ($form_state['storage']['step']) {
case 1:
$form['current-work-env'] = array(
'#type' => 'textarea',
'#title' => t('Describe your current work environment.'),
'#default_value' => isset($default_value['current-work-env']) ? $default_value['current-work-env'] : '',
);
$form['ideal-work-env'] = array(
'#type' => 'textarea',
'#title' => t('Describe your ideal work environment.'),
'#default_value' => isset($default_value['ideal-work-env']) ? $default_value['ideal-work-env'] : '',
);
break;
case 2:
$form['country'] = array(
'#type' => 'textfield',
'#title' => t('In what country do you live?'),
'#default_value' => isset($default_value['country']) ? $default_value['country'] : '',
);
$form['state'] = array(
'#type' => 'textfield',
'#title' => t('In what region or province do you live?'),
'#default_value' => isset($default_value['state']) ? $default_value['state'] : '',
);
break;
case 3:
$form['current-industry'] = array(
'#type' => 'textfield',
'#title' => t('In what industry do you currently work?'),
'#default_value' => isset($default_value['current-industry']) ? $default_value['current-industry'] : '',
);
$form['ideal-industry'] = array(
'#type' => 'textfield',
'#title' => t('In what industry would you ideally like to work?'),
'#default_value' => isset($default_value['ideal-industry']) ? $default_value['ideal-industry'] : '',
);
break;
case 4:
$form['thanks'] = array(
'#value' => '<p>'. t('Thank you for your participation in this survey.') .'</p>',
);
break;
}
//Depending on what page we're on, show the appropriate buttons.
if ($form_state['storage']['step'] > 1) {
$form['previous'] = array(
'#type' => 'submit',
'#value' => t('<< Previous'),
);
}
if ($form_state['storage']['step'] != 4) {
$form['next'] = array(
'#type' => 'submit',
'#value' => t('Continue >>'),
);
}
else {
$form['finish'] = array(
'#type' => 'submit',
'#value' => t('Finish'),
);
}
return $form;
}
/**
* The submit callback for the work_survey form.
*/
function work_survey_submit($form, &$form_state) {
//Save the values for the current step into the storage array.
$form_state['storage']['values'][$form_state['storage']['step']] = $form_state['values'];
//Check the button that was clicked and change the step.
if ($form_state['clicked_button']['#id'] == 'edit-previous') {
$form_state['storage']['step']--;
}
elseif ($form_state['clicked_button']['#id'] == 'edit-next') {
$form_state['storage']['step']++;
}
elseif ($form_state['clicked_button']['#id'] == 'edit-finish') {
//You should store the values from the form in the database here.
//We must do this or the form will rebuild instead of refreshing.
unset($form_state['storage']);
//Go to this page after completing the form.
$form_state['redirect'] = 'survey/work/thank-you';
}
}
?>The big trick here is that if $form_state['storage'] is set, the form will rebuild itself after clicking one of the submit buttons instead of refreshing itself and going back to page 1.
Hopefully this helps someone in the future!

April 6, 2010 - 4:15PM
A related tutorial for Drupal 7 has just been posted here.
May 17, 2010 - 3:35PM
This was quite helpful.
Thank you.
July 22, 2010 - 9:34AM
a suggestion that would make your site even more helpful - could you change the background colors - it's really impossible to read the code (the blue is the worst), so although I've had this page up for several days I still can't get myself to read it; instead I think I will copy/paste into a text editor. thanks!
July 28, 2010 - 11:58PM
I agree, the blue color is difficult to read against dark backgrounds, but the yellow color is hard to read against light backgrounds, and I also want to keep the site's theme consistent. So what I've done instead is made the blue color lighter so it contrasts better with the dark background. That makes the whole thing much easier to read I think.
October 8, 2010 - 7:43AM
Thanks A lot.
Good Tutorial
That's what i was looking for.
Is this all work for page refresh ??
October 21, 2010 - 6:21AM
This is really nice and helpful, but I ran into a problem. After the first time clicking I came to the second step and the all storage values get lost. So at step 2 the form ends. What did I make wrong? I used your example with other form names the logic is exactly the same....
Thx for the tutorial bronco
November 6, 2010 - 10:37PM
I'm not quite sure what you mean by "all storage values get lost" but make sure the submit function is actually running. The key line to storing the data between pages is:
<?php$form_state['storage']['values'][$form_state['storage']['step']] = $form_state['values'];
?>
So check that $form_state['storage'] gets correctly modified after each step.
November 7, 2010 - 12:49PM
Does it also work for browser back and refresh button on step 3 and forwared.?????
November 12, 2010 - 10:20PM
I think most modern browsers will remember what you wrote in forms on the previous page when you hit the Back button. But that's totally up to the browser, and there's absolutely nothing that can be done about browsers that don't do that.
It should work with the Refresh button, although again some old browsers will forget what you were writing, and there's nothing that can be done about that.
December 12, 2010 - 11:39PM
Thanks to Isaac for putting this together had been looking for quite some time for a guide such as this. I used this basic guide to put together a much more complex module to replace the std user registration form. The module we wrote also uses Content Profile node fields on some of the forms and includes a selection of different types of field handling. One of the main complexities over what is done here is the addition of required fields (which are not handled by the example above).
I did a bit of a write up of our module here: http://www.liquidcms.ca/multipage-user-registration-form. Hopefully it help out a few people still trying to figure some of this out.
Peter Lindstrom
LiquidCMS.ca
December 18, 2010 - 12:27AM
Thanks Peter! I would avoid clearing all messages at the end of the process since you may have messages that aren't related to that particular form queued up; I probably would have manually applied the "required" class to the relevant form elements and checked them myself in the validation function. However your implementation should be useful to many people -- it's certainly a common use case!
December 18, 2010 - 1:35PM
I used your example and made airline reservation system.
Thanks A lot for sharing code and helping.
January 5, 2011 - 9:29AM
hello sir,
i want to say thanks for that code. it works as charm and so understandable. i really was struggling to get this kind of form done. but this code helped me alot to finish my job. once again big thank you!
February 22, 2011 - 10:56AM
Brilliant tutorial and Drupal multi-step form example, these seem to be hard to come by!
March 25, 2011 - 10:20AM
Just used this form to create a 9 page form with forward and back buttons. There is a similar example in the Pro Drupal 6 Development book, but you'r approach is much easier to understand and is nicely expandable.
Great work! Keep it up :)
Post new comment