I am trying to implement a multistep registration form where if the user drops off the first step I then email them a link several weeks later for them to continue to step-2


After step two is complete the receive an email asking them to verify the account.

How do I load the users previous data if I send them directly to step2 at a later date. My ctools multistep currently looks like this after following this guide

function MYMODULE_registration_ctools_wizard($step = 'step-1') {
  // Include required ctools files.

  $form_info = array(
    // Specify unique form id for this form.
    'id' => 'multistep_registration',
    //Specify the path for this form. It is important to include space for the $step argument to be passed.
    'path' => "user/register/%step",
    // Show breadcrumb trail.
    'show trail' => TRUE,
    'show back' => FALSE,
    'show return' => FALSE,
    // Callback to use when the 'next' button is clicked.
    'next callback' => 'MYMODULE_registration_subtask_next',
    // Callback to use when entire form is completed.
    'finish callback' => 'MYMODULE_registration_subtask_finish',
    // Callback to use when user clicks final submit button.
    'return callback' => 'MYMODULE_registration_subtask_finish',
    // Callback to use when user cancels wizard.
    'cancel callback' => 'MYMODULE_registration_subtask_cancel',
    // Specify the order that the child forms will appear in, as well as their page titles.
    'order' => array(
      'step-1' => t('Step One'),
      'step-2' => t('Step Two'),
      'step-3' => t('Step Three'),
    // Define the child forms. Be sure to use the same keys here that were user in the 'order' section of this array.
    'forms' => array(
      'step-1' => array(
        'form id' => 'user_register_form'
      'step-2' => array(
        'form id' => 'MYMODULE_registration_group_info_form',
        // Be sure to load the required include file if the form callback is not defined in the .module file.
        //'include' => drupal_get_path('module', 'MYMODULE_registration') . '/MYMODULE_registration_groups_form.inc',
      'step-3' => array(
        'form id' => 'MYMODULE_registration_invite_form',

  // Make cached data available within each step's $form_state array.
  $form_state['signup_object'] = MYMODULE_registration_get_page_cache('signup');

  $output = ctools_wizard_multistep_form($form_info, $step, $form_state);

  return $output;

function MYMODULE_registration_get_page_cache($name) {
  $cache = ctools_object_cache_get('MYMODULE_registration', $name);
  // If the cached object doesn't exist yet, create an empty object.
  if (!$cache) {
    $cache = new stdClass();
    $cache->locked = ctools_object_cache_test('MYMODULE_registration', $name);

  return $cache;

2 Answers 2


you need to save your signup object in MYMODULE_registration_subtask_next


    function MYMODULE_registration_subtask_next (&$form_state) {
      MYMODULE_registration_set_page_cache('signup', $form_state['signup_object']);

and - in MYMODULE_registration_subtask_cancel and MYMODULE_registration_subtask_finish you should clean that cache, like MYMODULE_registration_remove_page_cache('signup');

  • I have updated the question to make it clearer, your answer only covers moving between steps in the same session. I would like to email the user a link to step 2 weeks later if they dropped off at step 1, hope that makes sense. Commented Jul 31, 2015 at 12:32
  • in that case you need to save the data after step one in a custom entity - or maybe use the user entity, giving them a special role or something (which you delete after completion), and them work through these objects in a cron job ...
    – rémy
    Commented Aug 3, 2015 at 8:11

I would say that the guide is not complete, it provides a way to store the data but it doesn't do anything with it.

For the values in the cache to be loaded upon each request, you should put them in $form_state['input'] just after loading them

  // Make cached data available within each step's $form_state array.
  $form_state['signup_object'] = MYMODULE_registration_get_page_cache('signup');
  // This will override values with cache if the user has not submitted something yet
  if (empty($form_state['input']) && $form_state['signup_object']) { 
    $form_state['input'] = $form_state['signup_object'];

Watch out for the PHP session expiration time (it should be over 2 weeks) as ctools uses that to retrieve cached data. You should aso add a cron that expire ctools cached data or you ctools cache will grow very big due to input by non-returning anonymous users.

  • I have added the get_page_cache function to my question which I think is already doing what you suggest (correct me if I am wrong). What I really wanted to do is add a token to the end of the link that I could send at any point and not rely on the session not being expired or a change of device. So when they hit step-2 url with the token the user details are fetched and the registration can continue. I think this is possible if I just delete the cache if the token exists retrieve the user and add the user object to the form somehow. Commented Aug 3, 2015 at 18:38
  • This seems pretty custom to me then, I would you do not need ctools to store your data, create your own table with hook_schema(), fill it with (mail, token, data, timestamp, sent) columns and add a cron that regularly send mail to timestamp older than 2 weeks. Then either a new parameter to your wizard callback for the token, or add a new menu route that set a session parameter which you can use in your form.
    – SebCorbin
    Commented Aug 4, 2015 at 7:16

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.