Overview

Sometimes we need to create custom Drupal forms to create or update Drupal Entities, in a different manner than the default forms that we are provided with. In this tutorial, we will look at creating a form 'wizard', that allows a user to create or edit two nodes in a single form.

Example overview

Throughout the world, many companies have multiple branches of their company. The form we will create in this tutorial will create a company, and then a branch that is assigned to that company. This will be done using a multistep form with the following steps:

  1. Create company
  2. Create branch
  3. Confirm values before saving

Technical overview

In this tutorial, we will create a module with a machine name of 'company_wizard'. This module will create a single page, on which our multistep form will be displayed.

This form will handle two different content (node) types. The first is 'company', which will simply contain the default node fields - title and body.

The next node type is 'branch', which will contain the default node fields of title and body, and will have an additional field of type entity reference (from the Entity Reference module), that will refer to the company node. The entity reference field will have the machine name field_branch_company.

In step one of our form, which will have the key 'create_company', we will set the title and body (description) of the node/company.

In step two of our form, which will have the key 'create_branch', we will set the title and body (description) of the code/branch.

Step three of the form, which will have the key 'confirm', will show the submitted values to the user before saving the nodes to the system.

Techniques used

There are a few specific techniques that will be used in this tutorial, that need to be looked at before diving into the form.

Passing entities to forms

The overall concept of our form is that we will use it as a sort of edit wrapper for our entities - in this case, the company and branch nodes. To do this, we will pass two nodes to the form definition. This form will actually serve a dual purpose - it will allow us to create a new company and/or branch, and it will allow us to edit existing companies and/or branches. As such we will either pass in empty nodes (to create new nodes) or existing nodes (to edit the existing nodes).

While this example uses nodes for its example, in fact any entity can be used instead of nodes, whether they be Drupal entities (user, taxonomy, comment etc), or PHP OOP entities that have nothing to do with Drupal whatsoever other than being edited in a Drupal form. The technique works the same regardless.

Using form snippets provided by the Drupal Field API

In order to ensure that our system is consistent as possible between the actual node forms, and our custom form that we will be creating, we will be using the function field_attach_form() to create our form elements for the body element of our nodes. This function allows us to get the form element as the field API defines it, based on the settings you have created through the form interface.

Altering elements by reference

The first steps of our form definition will be to save our nodes to the $form_state. We will be editing these nodes in the submit handlers of our various form steps, and as such, we need to alter the nodes directly in the $form_state, rather than editing a copy of the node. Therefore, throughout the tutorial you will code blocks like this:

span class="st0">'company'];

Note the & symbol in front of $form_state. This passes $form_state['company] by reference, rather than making a copy of $form_state['company']. This means that any changes made to $company, will also (or rather, in fact) be made to $form_state['company'], allowing us to preserve our values between steps.

Tutorial steps

The tutorial will break down into the following steps:

  1. Step 1: Create the page that will hold the form
  2. Step 2: Create the form definition that will call the form steps
  3. Step 3: Create our AJAX callback function
  4. Step 4: Create step one of our form: create company
  5. Step 5: Create step two of our form: create branch
  6. Step 6: Create step three of our form: confirm
  7. Step 7: Complete the form and return to step one

So, let's get to it!

Step 1: Create the page that will hold the form

The first thing we need to do in our tutorial is to create a path at which to access our form, using hook_menu(), and a page callback function in which to display the page that will show the form. I will not go into depth with these, and if you don't understand the concepts, then this tutorial is probably too advanced for you, and you should spend some time reading about hook_menu() and some time researching and understanding Drupal render arrays.

So first, our hook_menu() implementation

/**
 * Implementation of hook_menu()
 */// Define the system path /create_company
	$menu['create_company''title' => 'Create company',
		'page callback' => 'company_wizard_create_company_page',
		'access callback'

And next we will create our callback function for the path we defined above. In this function, we will do the following:

  1. Either load or create default $company and $branch nodes
  2. Pass these nodes to our form
  3. Make the form part of our page render array

/**
 * Callback function for the path /create_company
 *
 * @param int $company_id
 *   The ID of a company which should be updated/edited. Leave FALSE to create a new company
 * @param int $branch_id
 *   The ID of a branch which should be updated/edited. Leave FALSE to create a new branch
 *
 * @return array
 *   A render array representing the page
 */// If $company_id is not FALSE, we need to load
	// the company node with the given ID
// Load the company
// If the company node does not exist, we show the user a 'page not found' page
// $company_id was not set, so we create a new empty company node
'company');
	}
 
	// If $branch_id is not FALSE, we need to load
	// the branch node with the given ID
// Load the branch
// If the branch node does not exist, we show the user a 'page not found' page
// $branch_id was not set, so we create a new empty branch node
'branch');
	}
 
	// Now we define our page render array
'#prefix' => '<div id="company_wizard_create_company_page">',
		'#suffix' => '</div>',
		// We pass in the form ID of our wizard, as well as the $company and $branch
		// created above
		'form''company_wizard_create_company_form'

Now we have a page that will display our form, so the next thing to do is define our form.

Step 2: Create the form definition that will call the form steps

In this step, we will create the overall form. We will define the actual form elements for each step of the form in different functions (and steps of this tutorial). As such, this function defines more of a 'wrapper', so that we can track the various steps of our form quickly and easily.

The main points to note in this function are how we are storing the $company, the $branch, and the form step as part of $form_state, so that these can each be altered in future steps. Also note that any form elements common to every step of the form would be placed in this function, though in our example that is only one element: $form_state['actions'].

So on to the form definition

/**
 * The form callback that defines our multi-step form. This form creates
 * a company node, and a branch node, and sets the company node as a
 * node reference within the branch node.
 *
 * @param array $form
 *   The form object
 * @param array $form_state
 *   The form state of the form
 * @param stdClass $company
 *   A company node object that is either empty, when a new company is to be created, or is
 *   a populated company node if the company is to be edited
 * @param stdClass $branch
 *   A branch node object that is either empty, when a new branch is to be created, or is
 *   a populated branch node if the branch is to be edited
 *
 * @return array
 *   A Drupal render array defining the form
 */// As this is a multi-step form, in which we will be altering the $company node in the steps,
	// we want to set $form_state['company'] as the most up to date version of the company node. On
	// initial load, this will be the the $company node we passed in our arguments, but in
	// future steps, it will be the node contained in $form_state['company']. These two lines of code
	// ensure that the most up to date version of the node is retained
'company''company''company'] = $company;
 
	// As this is a multi-step form, in which we will be altering the $branch node in the steps,
	// we want to set $form_state['branch'] as the most up to date version of the company node. On
	// initial load, this will be the the $branch node we passed in our arguments, but in
	// future steps, it will be the node contained in $form_state['branch']. These two lines of code
	// ensure that the most up to date version of the node is retained
'branch''branch''branch'] = $branch;
 
	// As this is a multi-step form, we need to track which step we are on. We can do this
	// in $form_state['step']. As this value is not set on page load, we pass the name of
	// the first step of our form as the default: 'create_company'
'step''step'] : 'create_company''step'] = $step;
 
	// This is the form wrapper that will be used in our ajax callbacks, so that we can
	// load the various steps of the form.
'#prefix'] = '<div id="company_wizard_create_company_form_wrapper">''#suffix'] = '</div>';
 
	// Our form definition will consist of multiple steps. However, some elements may
	// be used in each step of the form. An example of this is the following element,
	// which will contain the forward and back buttons in our form. As such, we set this element
	// before calling the various steps of our form. However, as we have called it before other
	// form elements, it will appear above the rest of the form elements, which makes no sense,
	// so we set a heavy weight on it to ensure it is always the last element in the form.
'actions''#type' => 'actions',
		'#weight' => 999,
	);
 
	// Next we will set the form elements for the step of the form that we are on
// This section creates the form elements for the first step, create_company
'create_company'// This section creates the form elements for the second step, create_branch
'create_branch'// This section creates the form elements for the third step, confirm
'confirm'// Finally we return our completed form

We now have a form definition to which we can easily add new steps as necessary in the future.

Step 3: Create our AJAX callback function

One of the principles of our multistep form is that the entire form is replaced for each step. This means that we can use a common function as the #ajax callback for every submit button. So we will define that next. Since the entire form is replaced with each step, the entire $form is returned from our callback.

/**
 * The AJAX callback for the form company_wizard_create_company_form()
 *
 * @param array $form
 *   The form object
 * @param array $form_state
 *   The form state of the form
 *
 * This callback will be used as the ajax callback for all submit buttons on every step.
 *///Every submit button in our form will use the same wrapper, that wraps the
 	//entire form, and therefore, we need to return the entire form from our callback.

Step 4: Create step one of our form: create company

We now have a wrapper for our form in which our form steps are defined, and an AJAX callback that can be used by any buttons in our form. So the next thing to do is to create the form elements for each of the steps for our form. This next function will create the form elements for the first step of the form.

A couple key points to note here. First, the title of nodes is not a field created through the Drupal Field API, so we will create our own form element for this. The body field however is part of the Field API, so we will use field_attach_form() to create our body form element. However, in order for the form to not get confused between the body of the $company field and the $branch field, we need to assign it to a different element than the default.

Using field_attach_form(), any Field API form element can be attached to the form in the same manner as is shown for the body.

First, the definition of the form elements

/**
 * Form elements for the create_company step of company_wizard_create_company_form()
 *
 * @param array $form
 *   The form object
 * @param array $form_state
 *   The form state of the form
 *
 * @return array
 *   A Drupal render array defining the form elements for the create_company step of the form
 */// As this form is multistep, we want to preserve values saved from step to step for the situation
	// in which the user navigates back and forth through steps in the form. Therefore when required,
	// we will alter the $company node, in order to preserve these values. In the case in which an
	// existing company node is edited, the values will be pre-populated. Therefore, we need the
	// $company node to work with.
'company''company_name'// We could use the #required attribute of the form API to set our element as required, however
		// this will validate our field even when the back button is used, and the default text for required
		// form fields is a little too techy for regular users. Therefore, we add the form required asterisk
		// to our form title, but we will set our own validation further down to ensure the values is filled out.
		'#title' => t('Company name') . '<span title="' . t('This field is required.') . '" class="form-required">*</span>',
		'#type' => 'textfield',
		'#default_value''',
		// We want our title to be the top element of the form, so we set a light weight to it
		'#weight' => -999,
	);
 
	// Next, we want to attach the 'body' field to our form. We want to use the same body field as in
	// The actual node form, to ensure consistency across our site. As such, we use field_attach_form()
	// to generate the form element. This technique can be used to generate any field form snipped
	// that is part of a node form, simply submit 'body' for the field name of the element to be displayed
'node''field_name' => 'body'));
 
	// When using the above code, the node body field is attached to the form as $form['body']. However,
	// we will be using the body element of the $branch node in the next step. If both elements have the same
	// form key (body), the system gets confused, and mixes values. As such, we want our body element
	// to have a unique form key, and therefore we assign $form['body'] to $form['company_body'], and unset
	// $form['body'].
'company_body''body''body']);
 
	// On this step of the form, we need a single submit button, that will validate the submitted values
	// for this step, save the submitted values for this step, and set up the form for the next step.
'actions']['create_company_submit''#type' => 'submit',
		'#value' => t('Continue'),
		// #validate is an array containing the validation handler(s) for any elements in this step of the form
		'#validate''company_wizard_create_company_form_create_company_step_validate'),
		// #submit is an array containing the submit handler(s) for this step of the form
		'#submit''company_wizard_create_company_form_create_company_step_submit'),
		'#ajax'// This is the #prefix we set on the form in the original form definition
			'wrapper' => 'company_wizard_create_company_form_wrapper',
			// This is the ajax callback function we defined that will return our form
			'callback' => 'company_wizard_create_company_form_ajax_callback'

Next, validation of any elements created in this step

/**
 * Validation handler for the create_company step of company_wizard_create_company_form()
 *
 * This function will validate any form elements in the create_company step.
 *
 * @param array $form
 *   The form object
 * @param array $form_state
 *   The form state of the form
 */// The name of the company is required, so we check to ensure that the user has submitted a value,
	// and set an error if they have not.
'values']['company_name''company_name', t('Please enter the name of the company'));
	}
}

Finally, the submit handler for step one, that saves our values, and directs the user to the next step

/**
 * Submit handler for the create_company step of company_wizard_create_company_form()
 *
 * This function will save the submitted values to the $company node, and redirect the user
 * to the next step of the form.
 *
 * @param array $form
 *   The form object
 * @param array $form_state
 *   The form state of the form
 */// We will add our submitted values to the $company node, to preserve them through the various
	// form steps. As such, we want to access the node that we have stored in the $form_state. Note that
	// it is required to pass $form_state['company'] as a reference (by using the & symbol) so that any
	// changes we make to the node happen to the node we have saved in $form_state['company']
'company'];
 
	// Save the submitted title to $company->title
'values']['company_name'];
	// Save the submitted body to $company->body. This will save everything for the entire
	// body element, including the text format, and a summary if one has been set.
'values']['company_body'];
 
	// Set the next step of the form that is to be displayed to the user: create_branch
'step'] = 'create_branch';
	// $form_state['rebuild'] MUST be set to TRUE for each step of the form, in order for
	// the multistep form to work.
'rebuild'

The above three functions make up the first step of the form: definition, validation, and submission. Next, we will move on to the next step of our form, creating the branch.

Step 5: Create step two of our form: create branch

This step is mostly the same as the previous step, since we are only dealing with the same two types of fields: title and body. However, this form definition comes with an additional form element for the 'back' button, allowing the user to go back to the previous step. This button has its own submit handler, and therefore requires an additional function that the previous step didn't have (since there was no back button). Note that even in the back button, we save the submitted values, so that the user can come back to this step of the form and pick up where they left off.

First, the definition of the form elements

/**
 * Form elements for the create_branch step of company_wizard_create_company_form()
 *
 * @param array $form
 *   The form object
 * @param array $form_state
 *   The form state of the form
 *
 * @return array
 *   A Drupal render array defining the form elements for the create_branch step of the form
 */// As this form is multistep, we want to preserve values saved from step to step for the situation
	// in which the user navigates back and forth through steps in the form. Therefore, when required,
	// we will alter the $branch node, in order to preserve these values. In the case in which an
	// existing company node is edited, the values will be pre-populated. Therefore, we need the
	// $branch node to work with.
'branch''branch_name'// We could use the #required attribute of the form API to set our element as required, however
		// this will validate our field even when the back button is used, and the default text for required
		// form fields is a little too techy for regular users. Therefore, we add the form required asterisk
		// to our form title, but we will set our own validation further down to ensure the values is filled out.
		'#title' => t('Branch name') . '<span title="' . t('This field is required.') . '" class="form-required">*</span>',
		'#type' => 'textfield',
		'#default_value''',
		// We want our title to be the top element of the form, so we set a light weight to it
		'#weight' => -999,
	);
 
	// Next, we want to attach the 'body' field to our form. We want to use the same body field as in
	// The actual node form, to ensure consistency across our site. As such, we use field_attach_form()
	// to generate the form element. This technique can be used to generate any field form snipped
	// that is part of a node form, simply submit 'body' for the field name of the element to be displayed
'node''field_name' => 'body'));
 
	// When using the above code, the node body field is attached to the form as $form['body']. However,
	// we used the body element of the $company node in the previous step. If both elements have the same
	// form key (body), the system gets confused, and mixes values. As such, we want our body element
	// to have a unique form key, and therefore we assign $form['body'] to $form['branch_body'], and unset
	// $form['body'].
'branch_body''body''body']);
 
	// On this step of the form, we need two submit buttons. This is the first, that will validate the submitted
	// values for this step, save the submitted values for this step, and set up the form for the next step.
'actions']['create_branch_submit''#type' => 'submit',
		'#value' => t('Continue'),
		// #validate is an array containing the validation handler(s) for any elements in this step of the form
		'#validate''company_wizard_create_company_form_create_branch_step_validate'),
		// #submit is an array containing the submit handler(s) for this step of the form
		'#submit''company_wizard_create_company_form_create_branch_step_submit'),
		'#ajax''wrapper' => 'company_wizard_create_company_form_wrapper',
			'callback' => 'company_wizard_create_company_form_ajax_callback',
		),
	);
 
	// Next is the back button, that will take us to the previous step.
	// Notice that both buttons (and in fact every button in every step) use the exact same
	// #ajax callback and wrapper.
'actions']['create_branch_back''#type' => 'submit',
		'#value' => t('Back'),
		// #submit is an array containing the submit handler(s) for the back button. This will allow us
		// to redirect the user to the previous step in the form. Note that only want to validate the form
		// elements when going to the next step, and therefore no #validate handlers are attached to
		// this button.
		'#submit''company_wizard_create_company_form_create_branch_step_back'),
		'#ajax''wrapper' => 'company_wizard_create_company_form_wrapper',
			'callback' => 'company_wizard_create_company_form_ajax_callback'

Next, the submit handler for our back button

/**
 * Submit handler for the back button in the create_branch step of company_wizard_create_company_form()
 *
 * This function saves any elements submitted in the create_branch step of the form to the $branch node,
 * and redirects the user to the previous step.
 *
 * @param array $form
 *   The form object
 * @param array $form_state
 *   The form state of the form
 */// We will add our submitted values to the $branch node, to preserve them even though we are going
	// back to the previous step. This is so that if the user suddenly realizes they forgot something on the
	// previous step, any data they have entered on this step will not be lost when returning to this step.
	//  Note that it is required to pass $form_state['branch'] as a reference (by using the & symbol) so
	// that any changes we make to the node happen to the node we have saved in $form_state['branch']
'branch'];
 
	// Save the form elements from this step to the $branch node
'values']['branch_name''values']['branch_body'];
 
	// Direct the user to the previous step: create_company
'step'] = 'create_company';
	// $form_state['rebuild'] MUST be set to TRUE for each step of the form, in order for
	// the multistep form to work.
'rebuild'

Now, the validation handler for the form elements in this step

/**
 * Validation handler for the create_branch step of company_wizard_create_company_form()
 *
 * This function will validate any form elements in the create_branch step.
 *
 * @param array $form
 *   The form object
 * @param array $form_state
 *   The form state of the form
 */// The name of the branch is required, so we check to ensure that the user has submitted a value,
	// and set an error if they have not.
'values']['branch_name''branch_name', t('Please enter the name of the branch'));
	}
}

And finally, the submit handler to save the submitted values, and redirect the user to the next step

/**
 * Submit handler for the create_branch step of company_wizard_create_company_form()
 *
 * This function will save the submitted values to the $branch node, and redirect the user
 * to the next step of the form.
 *
 * @param array $form
 *   The form object
 * @param array $form_state
 *   The form state of the form
 */// We will add our submitted values to the $branch node, to preserve them through the various
	// form steps. As such, we want to access the node that we have stored in the $form_state. Note that
	// it is required to pass $form_state['branch'] as a reference (by using the & symbol) so that any
	// changes we make to the node happen to the node we have saved in $form_state['branch']
'branch'];
 
	// Save the form elements from this step to the $branch node
'values']['branch_name''values']['branch_body'];
 
	// Direct the user to the next step: confirm
'step'] = 'confirm';
	// $form_state['rebuild'] MUST be set to TRUE for each step of the form, in order for
	// the multistep form to work.
'rebuild'

The above four functions make up the second step of the form: definition, back button, validation, and submission. Next, we will move on to the final step of our form, showing the submitted values to the user for confirmation.

Step 6: Create step three of our form: confirm

The last step of our form will simply show the submitted values to the user, and give them the option to either save the data, or to go back, so that they can alter their submitted values. As such we need some text display, a back button, and a submit button. As there are no elements to add/change any form values in this step, we do not need validation.

First, the definition of the form elements

/**
 * Form elements for the confirm step of company_wizard_create_company_form()
 *
 * This step of the form shows the submitted values to the user as a final step, at which
 * point they can save their submission, or go back to edit it on previous pages.
 *
 * @param array $form
 *   The form object
 * @param array $form_state
 *   The form state of the form
 *
 * @return array
 *   A Drupal render array defining the form elements for the confirm step of the form
 */// We need our company node to display our submitted company values
'company'];
	// We need our branch node to display our submitted company values
'branch'];
 
	// Header
'confirm''#prefix' => '<p>',
		'#suffix' => '</p>',
		'#markup' => t('Would you like to create the company %company with the branch %branch?''%company' => $company->title, '%branch' => $branch->title)),
	);
 
	// Display the company output
'company_output''#prefix' => '<div id="company_output"><h2>'. t('Company') . '</h2>',
		'#suffix' => '</div>',
		'title''#prefix' => '<p>',
			'#suffix' => '</p>',
			'#markup' => t('Company Name: %title''%title' => $company->title)),
		),
		'body''#prefix' => '<div id="company_body">',
			'#suffix' => '</div>',
			'#markup''value''format']),
		),
	);
 
	// Display the branch output
'branch_output''#prefix' => '<div id="branch_output"><h2>'. t('Branch') . '</h2>',
		'#suffix' => '</div>',
		'title''#prefix' => '<p>',
			'#suffix' => '</p>',
			'#markup' => t('Branch Name: %title''%title' => $branch->title)),
		),
		'body''#prefix' => '<div id="branch_body">',
			'#suffix' => '</div>',
			'#markup''value''format']),
		),
	);
 
	// Add our submit button to complete the form
'actions']['confirm_submit''#type' => 'submit',
		'#value' => t('Create'),
		'#submit''company_wizard_create_company_form_confirm_step_submit'),
		'#ajax''wrapper' => 'company_wizard_create_company_form_wrapper',
			'callback' => 'company_wizard_create_company_form_ajax_callback',
		),
	);
 
	// Add our back button to go back to the previous step
'actions']['confirm_back''#type' => 'submit',
		'#value' => t('Back'),
		'#submit''company_wizard_create_company_form_confirm_step_back'),
		'#ajax''wrapper' => 'company_wizard_create_company_form_wrapper',
			'callback' => 'company_wizard_create_company_form_ajax_callback'

Next, the submit handler for our back button

/**
 * Submit handler for the back button in the confirm step of company_wizard_create_company_form()
 *
 * This function redirects the user to the previous step of the form: create_branch
 *
 * @param array $form
 *   The form object
 * @param array $form_state
 *   The form state of the form
 */// There are no form elements in this step, so we do not need to save anything to the $company or $branch nodes.
	// We simply need to redirect the user to the previous step.
 
	// Redirect the user to the previous step of the form, 'create_branch'
'step'] = 'create_branch';
	// $form_state['rebuild'] MUST be set to TRUE for each step of the form, in order for
	// the multistep form to work.
'rebuild'

Now we have our confirmation step to show to the users, and the final thing to do is to tie it altogether, and save our values to the database.

Step 7: Complete the form and return to step one

This last step is where all the magic takes place. In this step, we will take our completed $company and $branch nodes, and save them to the database. As we want to add an Entity Reference on the branch node pointing at the company node, we will add the NID of the company node to the branch node before saving. After all has been saved, if we are using the form to create new nodes, we need to replace our saved nodes with new empty nodes, and redirect to the top page. If we are editing existing nodes however, we do not want to replace these nodes, as the user may want to go through and edit again.

/**
 * Submit handler for the confirm step of company_wizard_create_company_form()
 *
 * This function completes the form, saving the new (or updated) $company and $branch
 * nodes, and adding the $company node as an entity reference of the branch node.
 *
 * @param array $form
 *   The form object
 * @param array $form_state
 *   The form state of the form
 */// We will be updating our $company and our $branch in this function, so we need them as reference.
'company''branch'];
 
	// We determine whether we are updating existing nodes, or creating new nodes. If nid is set for
	// either node, then the form was passed existing nodes at the start, and therefore we are updating.
// We prepare our company node by calling node_submit()
// We save our company node by calling node_save()
// If the company node has saved properly, then $node->nid will be set, and will be a value greater
	// than zero (which equate to TRUE)
// The company node has been successfully saved, therefore we want to set an entityreference
		// on the branch node, pointing at the company node.
'target_id'] = $company->nid;
		// We prepare our branch node by calling node_sumbit()
// We save our branch node by calling node_save()
// If the branch node has saved properly, then $branch->nid will be set, and will be a value greater
		// than zero (which equate to TRUE)
// The final actions performed are different depending on whether the user is updating existing
			// nodes, or creating new ones. First we handle the case in which the user is updating existing nodes
// Show the user a successful update message
'Thank you, the company !company with the branch !branch has been updated''!company' => l($company->title, 'node/' . $company->nid),
						'!branch' => l($branch->title, 'node/' . $branch->nid),
					))
				);
			}
			// Next we handle the case in which the user is creating new nodes
// Show the user a successful creation message
'Thank you, the company !company with the branch !branch has been created''!company' => l($company->title, 'node/' . $company->nid),
						'!branch' => l($branch->title, 'node/' . $branch->nid),
					))
				);
			}
 
			// Because we are on the form in which the user can create new companies and branches,
			// we need to set new empty node objects in $form_state, so that after the user is redirected
			// back to the first step of the form, they can create a new company and new branch.
'company''branch'// For some reason, our branch node did not save, so we show an error to the user, and save the data
			// to the watchdog table for debugging.
'Sorry, an error occurred when saving the branch''company_wizard_save_company', t('Error in following branch node: !error''!errror' => '<pre>''</pre>'// For some reason, our company node did not save, so we show an error to the user, and save the data
		// to the watchdog table for debugging.
'Sorry, an error occurred when saving the company''company_wizard_save_company', t('Error in following company node: !error''!errror' => '<pre>''</pre>')));
	}
 
	// We redirect the user back to the first step of the form, create_company
'step'] = 'create_company';
	// $form_state['rebuild'] MUST be set to TRUE for each step of the form, in order for
	// the multistep form to work.
'rebuild'

And with that, our form is complete.

Summary

In this tutorial, we learned how to create a 'wizard' form in Drupal, in order to create/edit either a single or multiple Entities/nodes through a single form that is different from the default node or entity forms provided by Drupal or other modules. We used the function field_attach_form() to ensure consistency between the default node forms and our custom form, and we set our entire form to be an ajax multistep form, providing a nice user experience to our users, from within Drupal.

Entire code

Below is the entire code used in this tutorial

/**
 * Implementation of hook_menu()
 */// Define the system path /create_company
	$menu['create_company''title' => 'Create company',
		'page callback' => 'company_wizard_create_company_page',
		'access callback'/**
 * Callback function for the path /create_company
 *
 * @param int $company_id
 *   The ID of a company which should be updated/edited. Leave FALSE to create a new company
 * @param int $branch_id
 *   The ID of a branch which should be updated/edited. Leave FALSE to create a new branch
 *
 * @return array
 *   A render array representing the page
 */// If $company_id is not FALSE, we need to load
	// the company node with the given ID
// Load the company
// If the company node does not exist, we show the user a 'page not found' page
// $company_id was not set, so we create a new empty company node
'company');
	}
 
	// If $branch_id is not FALSE, we need to load
	// the branch node with the given ID
// Load the branch
// If the branch node does not exist, we show the user a 'page not found' page
// $branch_id was not set, so we create a new empty branch node
'branch');
	}
 
	// Now we define our page render array
'#prefix' => '<div id="company_wizard_create_company_page">',
		'#suffix' => '</div>',
		// We pass in the form ID of our wizard, as well as the $company and $branch
		// created above
		'form''company_wizard_create_company_form'/**
 * Helper function to create empty nodes that will be used in our form
 *
 * @param string $type
 *   The type of node to be created. Supported values:
 *   - company
 *   - branch
 *
 * @return stdClass
 *   An empty node of the type requested
 */// If an incorrect type is sent, we want to return FALSE,
	// so we set the default at the start.
'company')
	{
		// Create an empty company node
'title' => '',
			'type' => 'company',
			'language''branch')
	{
		// Create an empty branch node
'title' => '',
			'type' => 'branch',
			'language''field_branch_company'// Return either our empty node, or FALSE if an incorrect
	// type was passed.
/**
 * The form callback that defines our multi-step form. This form creates
 * a company node, and a branch node, and sets the company node as a
 * node reference within the branch node.
 *
 * @param array $form
 *   The form object
 * @param array $form_state
 *   The form state of the form
 * @param stdClass $company
 *   A company node object that is either empty, when a new company is to be created, or is
 *   a populated company node if the company is to be edited
 * @param stdClass $branch
 *   A branch node object that is either empty, when a new branch is to be created, or is
 *   a populated branch node if the branch is to be edited
 *
 * @return array
 *   A Drupal render array defining the form
 */// As this is a multi-step form, in which we will be altering the $company node in the steps,
	// we want to set $form_state['company'] as the most up to date version of the company node. On
	// initial load, this will be the the $company node we passed in our arguments, but in
	// future steps, it will be the node contained in $form_state['company']. These two lines of code
	// ensure that the most up to date version of the node is retained
'company''company''company'] = $company;
 
	// As this is a multi-step form, in which we will be altering the $branch node in the steps,
	// we want to set $form_state['branch'] as the most up to date version of the company node. On
	// initial load, this will be the the $branch node we passed in our arguments, but in
	// future steps, it will be the node contained in $form_state['branch']. These two lines of code
	// ensure that the most up to date version of the node is retained
'branch''branch''branch'] = $branch;
 
	// As this is a multi-step form, we need to track which step we are on. We can do this
	// in $form_state['step']. As this value is not set on page load, we pass the name of
	// the first step of our form as the default: 'create_company'
'step''step'] : 'create_company''step'] = $step;
 
	// This is the form wrapper that will be used in our ajax callbacks, so that we can
	// load the various steps of the form.
'#prefix'] = '<div id="company_wizard_create_company_form_wrapper">''#suffix'] = '</div>';
 
	// Our form definition will consist of multiple steps. However, some elements may
	// be used in each step of the form. An example of this is the following element,
	// which will contain the forward and back buttons in our form. As such, we set this element
	// before calling the various steps of our form. However, as we have called it before other
	// form elements, it will appear above the rest of the form elements, which makes no sense,
	// so we set a heavy weight on it to ensure it is always the last element in the form.
'actions''#type' => 'actions',
		'#weight' => 999,
	);
 
	// Next we will set the form elements for the step of the form that we are on
// This section creates the form elements for the first step, create_company
'create_company'// This section creates the form elements for the second step, create_branch
'create_branch'// This section creates the form elements for the third step, confirm
'confirm'// Finally we return our completed form
/**
 * The AJAX callback for the form company_wizard_create_company_form()
 *
 * @param array $form
 *   The form object
 * @param array $form_state
 *   The form state of the form
 *
 * This callback will be used as the ajax callback for all submit buttons on every step.
 *///Every submit button in our form will use the same wrapper, that wraps the
 	//entire form, and therefore, we need to return the entire form from our callback.
/**
 * Form elements for the create_company step of company_wizard_create_company_form()
 *
 * @param array $form
 *   The form object
 * @param array $form_state
 *   The form state of the form
 *
 * @return array
 *   A Drupal render array defining the form elements for the create_company step of the form
 */// As this form is multistep, we want to preserve values saved from step to step for the situation
	// in which the user navigates back and forth through steps in the form. Therefore when required,
	// we will alter the $company node, in order to preserve these values. In the case in which an
	// existing company node is edited, the values will be pre-populated. Therefore, we need the
	// $company node to work with.
'company''company_name'// We could use the #required attribute of the form API to set our element as required, however
		// this will validate our field even when the back button is used, and the default text for required
		// form fields is a little too techy for regular users. Therefore, we add the form required asterisk
		// to our form title, but we will set our own validation further down to ensure the values is filled out.
		'#title' => t('Company name') . '<span title="' . t('This field is required.') . '" class="form-required">*</span>',
		'#type' => 'textfield',
		'#default_value''',
		// We want our title to be the top element of the form, so we set a light weight to it
		'#weight' => -999,
	);
 
	// Next, we want to attach the 'body' field to our form. We want to use the same body field as in
	// The actual node form, to ensure consistency across our site. As such, we use field_attach_form()
	// to generate the form element. This technique can be used to generate any field form snipped
	// that is part of a node form, simply submit 'body' for the field name of the element to be displayed
'node''field_name' => 'body'));
 
	// When using the above code, the node body field is attached to the form as $form['body']. However,
	// we will be using the body element of the $branch node in the next step. If both elements have the same
	// form key (body), the system gets confused, and mixes values. As such, we want our body element
	// to have a unique form key, and therefore we assign $form['body'] to $form['company_body'], and unset
	// $form['body'].
'company_body''body''body']);
 
	// On this step of the form, we need a single submit button, that will validate the submitted values
	// for this step, save the submitted values for this step, and set up the form for the next step.
'actions']['create_company_submit''#type' => 'submit',
		'#value' => t('Continue'),
		// #validate is an array containing the validation handler(s) for any elements in this step of the form
		'#validate''company_wizard_create_company_form_create_company_step_validate'),
		// #submit is an array containing the submit handler(s) for this step of the form
		'#submit''company_wizard_create_company_form_create_company_step_submit'),
		'#ajax'// This is the #prefix we set on the form in the original form definition
			'wrapper' => 'company_wizard_create_company_form_wrapper',
			// This is the ajax callback function we defined that will return our form
			'callback' => 'company_wizard_create_company_form_ajax_callback'/**
 * Validation handler for the create_company step of company_wizard_create_company_form()
 *
 * This function will validate any form elements in the create_company step.
 *
 * @param array $form
 *   The form object
 * @param array $form_state
 *   The form state of the form
 */// The name of the company is required, so we check to ensure that the user has submitted a value,
	// and set an error if they have not.
'values']['company_name''company_name', t('Please enter the name of the company'));
	}
}
 
/**
 * Submit handler for the create_company step of company_wizard_create_company_form()
 *
 * This function will save the submitted values to the $company node, and redirect the user
 * to the next step of the form.
 *
 * @param array $form
 *   The form object
 * @param array $form_state
 *   The form state of the form
 */// We will add our submitted values to the $company node, to preserve them through the various
	// form steps. As such, we want to access the node that we have stored in the $form_state. Note that
	// it is required to pass $form_state['company'] as a reference (by using the & symbol) so that any
	// changes we make to the node happen to the node we have saved in $form_state['company']
'company'];
 
	// Save the submitted title to $company->title
'values']['company_name'];
	// Save the submitted body to $company->body. This will save everything for the entire
	// body element, including the text format, and a summary if one has been set.
'values']['company_body'];
 
	// Set the next step of the form that is to be displayed to the user: create_branch
'step'] = 'create_branch';
	// $form_state['rebuild'] MUST be set to TRUE for each step of the form, in order for
	// the multistep form to work.
'rebuild'/**
 * Form elements for the create_branch step of company_wizard_create_company_form()
 *
 * @param array $form
 *   The form object
 * @param array $form_state
 *   The form state of the form
 *
 * @return array
 *   A Drupal render array defining the form elements for the create_branch step of the form
 */// As this form is multistep, we want to preserve values saved from step to step for the situation
	// in which the user navigates back and forth through steps in the form. Therefore, when required,
	// we will alter the $branch node, in order to preserve these values. In the case in which an
	// existing company node is edited, the values will be pre-populated. Therefore, we need the
	// $branch node to work with.
'branch''branch_name'// We could use the #required attribute of the form API to set our element as required, however
		// this will validate our field even when the back button is used, and the default text for required
		// form fields is a little too techy for regular users. Therefore, we add the form required asterisk
		// to our form title, but we will set our own validation further down to ensure the values is filled out.
		'#title' => t('Branch name') . '<span title="' . t('This field is required.') . '" class="form-required">*</span>',
		'#type' => 'textfield',
		'#default_value''',
		// We want our title to be the top element of the form, so we set a light weight to it
		'#weight' => -999,
	);
 
	// Next, we want to attach the 'body' field to our form. We want to use the same body field as in
	// The actual node form, to ensure consistency across our site. As such, we use field_attach_form()
	// to generate the form element. This technique can be used to generate any field form snipped
	// that is part of a node form, simply submit 'body' for the field name of the element to be displayed
'node''field_name' => 'body'));
 
	// When using the above code, the node body field is attached to the form as $form['body']. However,
	// we used the body element of the $company node in the previous step. If both elements have the same
	// form key (body), the system gets confused, and mixes values. As such, we want our body element
	// to have a unique form key, and therefore we assign $form['body'] to $form['branch_body'], and unset
	// $form['body'].
'branch_body''body''body']);
 
	// On this step of the form, we need two submit buttons. This is the first, that will validate the submitted
	// values for this step, save the submitted values for this step, and set up the form for the next step.
'actions']['create_branch_submit''#type' => 'submit',
		'#value' => t('Continue'),
		// #validate is an array containing the validation handler(s) for any elements in this step of the form
		'#validate''company_wizard_create_company_form_create_branch_step_validate'),
		// #submit is an array containing the submit handler(s) for this step of the form
		'#submit''company_wizard_create_company_form_create_branch_step_submit'),
		'#ajax''wrapper' => 'company_wizard_create_company_form_wrapper',
			'callback' => 'company_wizard_create_company_form_ajax_callback',
		),
	);
 
	// Next is the back button, that will take us to the previous step.
	// Notice that both buttons (and in fact every button in every step) use the exact same
	// #ajax callback and wrapper.
'actions']['create_branch_back''#type' => 'submit',
		'#value' => t('Back'),
		// #submit is an array containing the submit handler(s) for the back button. This will allow us
		// to redirect the user to the previous step in the form. Note that only want to validate the form
		// elements when going to the next step, and therefore no #validate handlers are attached to
		// this button.
		'#submit''company_wizard_create_company_form_create_branch_step_back'),
		'#ajax''wrapper' => 'company_wizard_create_company_form_wrapper',
			'callback' => 'company_wizard_create_company_form_ajax_callback'/**
 * Submit handler for the back button in the create_branch step of company_wizard_create_company_form()
 *
 * This function saves any elements submitted in the create_branch step of the form to the $branch node,
 * and redirects the user to the previous step.
 *
 * @param array $form
 *   The form object
 * @param array $form_state
 *   The form state of the form
 */// We will add our submitted values to the $branch node, to preserve them even though we are going
	// back to the previous step. This is so that if the user suddenly realizes they forgot something on the
	// previous step, any data they have entered on this step will not be lost when returning to this step.
	//  Note that it is required to pass $form_state['branch'] as a reference (by using the & symbol) so
	// that any changes we make to the node happen to the node we have saved in $form_state['branch']
'branch'];
 
	// Save the form elements from this step to the $branch node
'values']['branch_name''values']['branch_body'];
 
	// Direct the user to the previous step: create_company
'step'] = 'create_company';
	// $form_state['rebuild'] MUST be set to TRUE for each step of the form, in order for
	// the multistep form to work.
'rebuild'/**
 * Validation handler for the create_branch step of company_wizard_create_company_form()
 *
 * This function will validate any form elements in the create_branch step.
 *
 * @param array $form
 *   The form object
 * @param array $form_state
 *   The form state of the form
 */// The name of the branch is required, so we check to ensure that the user has submitted a value,
	// and set an error if they have not.
'values']['branch_name''branch_name', t('Please enter the name of the branch'));
	}
}
 
/**
 * Submit handler for the create_branch step of company_wizard_create_company_form()
 *
 * This function will save the submitted values to the $branch node, and redirect the user
 * to the next step of the form.
 *
 * @param array $form
 *   The form object
 * @param array $form_state
 *   The form state of the form
 */// We will add our submitted values to the $branch node, to preserve them through the various
	// form steps. As such, we want to access the node that we have stored in the $form_state. Note that
	// it is required to pass $form_state['branch'] as a reference (by using the & symbol) so that any
	// changes we make to the node happen to the node we have saved in $form_state['branch']
'branch'];
 
	// Save the form elements from this step to the $branch node
'values']['branch_name''values']['branch_body'];
 
	// Direct the user to the next step: confirm
'step'] = 'confirm';
	// $form_state['rebuild'] MUST be set to TRUE for each step of the form, in order for
	// the multistep form to work.
'rebuild'/**
 * Form elements for the confirm step of company_wizard_create_company_form()
 *
 * This step of the form shows the submitted values to the user as a final step, at which
 * point they can save their submission, or go back to edit it on previous pages.
 *
 * @param array $form
 *   The form object
 * @param array $form_state
 *   The form state of the form
 *
 * @return array
 *   A Drupal render array defining the form elements for the confirm step of the form
 */// We need our company node to display our submitted company values
'company'];
	// We need our branch node to display our submitted company values
'branch'];
 
	// Header
'confirm''#prefix' => '<p>',
		'#suffix' => '</p>',
		'#markup' => t('Would you like to create the company %company with the branch %branch?''%company' => $company->title, '%branch' => $branch->title)),
	);
 
	// Display the company output
'company_output''#prefix' => '<div id="company_output"><h2>'. t('Company') . '</h2>',
		'#suffix' => '</div>',
		'title''#prefix' => '<p>',
			'#suffix' => '</p>',
			'#markup' => t('Company Name: %title''%title' => $company->title)),
		),
		'body''#prefix' => '<div id="company_body">',
			'#suffix' => '</div>',
			'#markup''value''format']),
		),
	);
 
	// Display the branch output
'branch_output''#prefix' => '<div id="branch_output"><h2>'. t('Branch') . '</h2>',
		'#suffix' => '</div>',
		'title''#prefix' => '<p>',
			'#suffix' => '</p>',
			'#markup' => t('Branch Name: %title''%title' => $branch->title)),
		),
		'body''#prefix' => '<div id="branch_body">',
			'#suffix' => '</div>',
			'#markup''value''format']),
		),
	);
 
	// Add our submit button to complete the form
'actions']['confirm_submit''#type' => 'submit',
		'#value' => t('Create'),
		'#submit''company_wizard_create_company_form_confirm_step_submit'),
		'#ajax''wrapper' => 'company_wizard_create_company_form_wrapper',
			'callback' => 'company_wizard_create_company_form_ajax_callback',
		),
	);
 
	// Add our back button to go back to the previous step
'actions']['confirm_back''#type' => 'submit',
		'#value' => t('Back'),
		'#submit''company_wizard_create_company_form_confirm_step_back'),
		'#ajax''wrapper' => 'company_wizard_create_company_form_wrapper',
			'callback' => 'company_wizard_create_company_form_ajax_callback'/**
 * Submit handler for the back button in the confirm step of company_wizard_create_company_form()
 *
 * This function redirects the user to the previous step of the form: create_branch
 *
 * @param array $form
 *   The form object
 * @param array $form_state
 *   The form state of the form
 */// Redirect the user to the previous step of the form, 'create_branch'
'step'] = 'create_branch';
	// $form_state['rebuild'] MUST be set to TRUE for each step of the form, in order for
	// the multistep form to work.
'rebuild'/**
 * Submit handler for the confirm step of company_wizard_create_company_form()
 *
 * This function completes the form, saving the new (or updated) $company and $branch
 * nodes, and adding the $company node as an entity reference of the branch node.
 *
 * @param array $form
 *   The form object
 * @param array $form_state
 *   The form state of the form
 */// We will be updating our $company and our $branch in this function, so we need them as reference.
'company''branch'];
 
	// We determine whether we are updating existing nodes, or creating new nodes. If nid is set for
	// either node, then the form was passed existing nodes at the start, and therefore we are updating.
// We prepare our company node by calling node_submit()
// We save our company node by calling node_save()
// If the company node has saved properly, then $node->nid will be set, and will be a value greater
	// than zero (which equate to TRUE)
// The company node has been successfully saved, therefore we want to set an entityreference
		// on the branch node, pointing at the company node.
'target_id'] = $company->nid;
		// We prepare our branch node by calling node_sumbit()
// We save our branch node by calling node_save()
// If the branch node has saved properly, then $branch->nid will be set, and will be a value greater
		// than zero (which equate to TRUE)
// The final actions performed are different depending on whether the user is updating existing
			// nodes, or creating new ones. First we handle the case in which the user is updating existing nodes
// Show the user a successful update message
'Thank you, the company !company with the branch !branch has been updated''!company' => l($company->title, 'node/' . $company->nid),
						'!branch' => l($branch->title, 'node/' . $branch->nid),
					))
				);
			}
			// Next we handle the case in which the user is creating new nodes
// Show the user a successful creation message
'Thank you, the company !company with the branch !branch has been created''!company' => l($company->title, 'node/' . $company->nid),
						'!branch' => l($branch->title, 'node/' . $branch->nid),
					))
				);
			}
 
			// Because we are on the form in which the user can create new companies and branches,
			// we need to set new empty node objects in $form_state, so that after the user is redirected
			// back to the first step of the form, they can create a new company and new branch.
'company''branch'// For some reason, our branch node did not save, so we show an error to the user, and save the data
			// to the watchdog table for debugging.
'Sorry, an error occurred when saving the branch''company_wizard_save_company', t('Error in following branch node: !error''!errror' => '<pre>''</pre>'// For some reason, our company node did not save, so we show an error to the user, and save the data
		// to the watchdog table for debugging.
'Sorry, an error occurred when saving the company''company_wizard_save_company', t('Error in following company node: !error''!errror' => '<pre>''</pre>')));
	}
 
	// We redirect the user back to the first step of the form, create_company
'step'] = 'create_company';
	// $form_state['rebuild'] MUST be set to TRUE for each step of the form, in order for
	// the multistep form to work.
'rebuild'