Home / Tutorials / Multi-step Form in Drupal 8

Multi-step Form in Drupal 8

we are going to look at building a multistep form in Drupal 8. For brevity, the form will have only two steps in the shape of two completely separate forms. To persist values across these steps, we will use functionality provided by Drupal’s core for storing temporary and private data across multiple requests.

In Drupal 7, a similar approach can be achieved using the cTools object cache. Alternatively, there is the option of persisting data through the $form_state array as illustrated in this tutorial.

The code we write in this article can be found in this repository alongside much of the Drupal 8 work we’ve been doing so far. We will be dealing with forms quite a lot so I do recommend checking out one of the previous articles on Drupal 8 in which we talk about forms.

The plan

As I mentioned above, our multistep form will consist of two independent forms with two simple elements each. Users will be able to fill in the first one and move to the second form where they can either go back to the previous step or fill it in and press submit. While navigating between the different steps, the previously submitted values are stored and used to pre-populate the form fields. If the last form is submitted, however, the data gets processed (not covered in this article) and cleared from the temporary storage.

Technically, both of these forms will inherit common functionality from an abstract form class we will call MultistepFormBase. This class will be in charge of injecting the necessary dependencies, scaffolding the form, processing the end result and anything else that is needed and is common to both.

We will group all the form classes together and place them inside a new folder called Multistep located within the Form plugin directory of our demo module (next to the old DemoForm). This is purely for having a clean structure and being able to quickly tell which forms are part of our multistep form process.

The code

We will start with the form base class. I will explain what is going on here after we see the code.



Our abstract form class extends from the default Drupal FormBase class so that we can use some of the functionality made available by it and the traits it uses. We are using dependency injection to inject some of the needed services:

  • PrivateTempStoreFactory gives us a temporary store that is private to the current user (PrivateTempStore). We will keep all the submitted data from the form steps in this store. In the constructor, we are also immediately saving the store attribute which contains a reference to the multistep_data key/value collection we will use for this process. The get() method on the factory either creates the store if it doesn’t exist or retrieves it from the storage.
  • The SessionManager allows us to start a session for anonymous users.
  • The CurrentUser allows us to check if the current user is anonymous.

Inside the buildForm() method we do two main things. First, we start a session for anonymous users if one does’t already exist. This is because without a session we cannot pass around temporary data across multiple requests. We use the session manager for this. Second, we create a base submit action button that will be present on all the implementing forms.

The saveData() method is going to be called from one or more of the implementing forms and is responsible with persisting the data from the temporary storage once the multistep process is completed. We won’t be going into the details of this implementation because it depends entirely on your use case (e.g. you can create a configuration entity from each submission). We do, however, handle the removal of all the items in the store once the data has been persisted. Keep in mind though that these types of logic checks should not be performed in the base class. You should defer to a dedicated service class as usual, or use a similar approach.

Now it’s time for the actual forms that will represent steps in the process. We start with the first class inside a file called MultistepOneForm.php:

This form will look something like this:


In the buildForm() method we are defining our two dummy form elements. Do notice that we are retrieving the existing form definition from the parent class first. The default values for these fields are set as the values found in the store for those keys (so that users can see the values they filled in at this step if they come back to it). Finally, we are changing the value of the action button to Next (to indicate that this form is not the final one).

In the submitForm() method we save the submitted values to the store and then redirect to the second form (which can be found at the route demo.multistep_two). Keep in mind that we are not doing any sort of validation here to keep the code light. But most use cases will call for some input validation.

Since we’ve touched upon the issue of routes, let’s update the route file in our demo module and create two new routes for our forms:


For more information about what is going on in this file you can read one of the previous Drupal 8 articles which explain routes as well.

Finally, we can create our second form (inside a file called MultistepTwoForm):


This one will look like this, again very simple:


Again, we are extending from our base class like we did with the first form. This time, however, we have different form elements and we are adding a new action link next to the submit button. This will allow users to navigate back to the first step of the form process.

Inside the submitForm() method we again save the values to the store and defer to the parent class to persist this data in any way it sees fit. We then redirect to whatever page we want (the route we use here is a dummy one).

And that is pretty much it. We should now have a working multistep form that uses the PrivateTempStore to keep data available across multiple requests. If we need more steps, all we have to do is create some more forms, add them in between the existing ones and make a couple of adjustments. Of course you can make this much more flexible by not hardcoding the route names in the links and redirects, but I leave that part up to you.


About cmadmin

Web Developer & Designer | Android App Developer

Check Also

Building a Drupal 8 Module – EntityFieldQuery

Even though Drupal 7 core fell short of a proper way of handling its brand …

Understanding Drupal EntityFieldQuery

When building complex web apps, you’ll eventually have to interact with a database. To retrieve …


Create an Account!
Forgot Password? (close)

Sign Up

Confirm Password
Want to Login? (close)

Forget Password?

Username or Email
To get latest new / tutorial / technology / development information subscribe with us.
Lets Get Updated with latest trends & tutorials!
Your Information will never be shared with any third party.
Ready for latest tutorials & tools !