Getting Started with Forms in Drupal 8

Author's note: This blog post was written during the Drupal 8 development cycle. A lot has changed since then and the information in this blog post a bit out of date. Drupal now uses PSR-4 for autoloading, and hook_menu() has been removed in favor of the new routing system. For more up-to-date information check out our series on Drupal 8 module development essentials.

Forms are an essential part of any web application. They are the primary mechanism for collecting input from our users, and without them Drupal wouldn't be very useful. As such, they're also one of the first things people want to learn when they start learning Drupal. Forms are fundamental to creating Drupal modules, whether you're asking someone to leave a review of your video or giving an administrator the option to turn JavaScript aggregation off.

Form basics

There are two key elements to crafting forms.  The workflow a form goes through, including how Drupal locates the form to display on a page, handling validation when someone submits a form, and ultimately doing something with the collected data. And the definition of the form itself, in which one determines if your form will have checkboxes, textfields, upload widgets, and/or any user-facing text.

Form definition

The way that forms are defined in Drupal hasn't changed that much between Drupal 7 and Drupal 8, and I'm not going to go into too much detail here. Form definitions are still a Drupal render array made up of Form API elements that are ultimately parsed down to the HTML that is presented to the browser. The biggest change to crafting forms is the addition of some new HTML5 elements that can be defined in the Form API array, like tel number date.

Watch our Drupal 7 Module Developement series for a few lessons about using the Form API and form arrays in Drupal 7.

Form workflow

Truth be told, form workflow hasn't changed that much at a high level  either. We still have the concepts of building, validation, and submission. And they're still all available for us to hook into by simply conforming to a specific pattern. It's really just the pattern that has changed. So lets take a look at that.

With the move to more modern PHP usage and Object Oriented Progamming patterns in Drupal 8,  we now have the concept of form objects defined by a form class. All form classes implement the Drupal\Core\Form\FormInterface interface, which states that any form object should have getFormId, buildForm, validateForm, and submitForm methods. It turns out that this matches up nicely with the build, validate, and submit workflow. By conforming to this interface, we ensure that Drupal knows how to process each step of the workflow for the form in question, given any form object. Before we look at some sample code, however, lets talk just a little bit more about a typical form workflow.

When a user visits a URL on a site,  /contact for example, Drupal needs to return the HTML representation of the required form so that it can be displayed in the user's browser. In order to get that form definition, Drupal loads the required form object. Then Drupal calls the buildForm() method on that object. This returns a Form API array that Drupal can turn into HTML. Likely this HTML includes also a button that a user can click. Clicking the button generates an HTTP Post request to the URL defined as the action of the form. In Drupal, this is the same URl in which the form is displayed (i.e., /contact). 

This time, however, when Drupal gets the request for /contact it notices also that the request contains $_POST data. This means that the form being requested has actually just been submitted, and it should proceed to the next step in the workflow, which is validation. So Drupal instantiates our form object and calls the validateForm() method, which it knows is present because we're implementing the FormInterface. If the validation handler determines there are any errors in the data it flags them, and Drupal halts processing. It displays the form to the user to get errors fixed, and then it waits for the user to submit the form again before proceding. If no errors are found, Drupal moves on to the submission step of the workflow by calling our form objects submitForm(). Here we perform whatever logic is necessary with the data we collect in the form, like save it to the database or a config file.

Once you know how it works, the entire process is actually quite simple and beautiful. And it hasn't changed all that much, even since the Drupal 4.7 era. Many people love to hate it, but it's easy to argue that Form API is one of the strongest features in Drupal.

Show me some code already!

Ready to wire it all up? The first thing you'll need to do is create a route for your form. In our example, it looks like this:


chad.settings:
  path: '/admin/config/system/chad'
  defaults:
    _form: 'Drupal\chad\Form\SettingsForm'
    _title: 'Chad Settings'
  requirements:
    _permission: 'administer site configuration'

The only difference between this route and one that displays non-form content on a page is the _form key instead of the usual _content key. Here _form tells Drupal the location of the class that it should use when constructing our form object. Note that we simply specify the class name here and not the method, like SettingsForm::buildForm. Because we've defined this route as a form, Drupal will call buildForm whenever someone requests /admin/config/system/chad.

Our form class then looks like the following and lives in lib/Drupal/chad/Form/SettingsForm.php


/**
 * @file
 */

namespace Drupal\chad\Form;

use Drupal\Core\Form\ConfigFormBase;

class SettingsForm extends ConfigFormBase {

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'chad_settings';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, array &$form_state) {

    // Build our Form API array here.

    return parent::buildForm($form, $form_state);
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, array &$form_state) {

    // Handle submitted values in $form_state here.

    return parent::submitForm($form, $form_state);
  }

}

Also note that we've opted to extend the Drupal\Core\Form\ConfigFormBase class which provides some additional boilerplate code for system settings forms. There is a Drupal\Core\Form\FormBase class also. This is a great starting point for most forms because it handles injection of common dependencies. Nevertheless, anything that implements the FormInterface will work.

See the previous post in this series, Drupal 8: Writing a Hello World Module, for background on the code this video utilizes.

Finally, watch the video to see it all wired together and working:

Sample code.

Comments

This looks like a great way to handle some of the fairly boilerplate code needed for creating forms. Thanks for sharing. Seems like a really good tool to start making use of after you understand the basics of how forms work.

totally agree with you cover the basics about form is a must same for controller, routing, services, etc

the main goal of this project is to speeding up the process of starting a new project and avoid the repetitive tasks.

Nice tutorial but can you explain. How we will save the form with AJAX.

Hey Tarun,

I don't yet have any experience with the AJAX API in Drupal 8 so I can't really give a complete answer to this question. I would start by looking at the AJAX API documentation. Though I did take a quick look at it and it seems to be a bit out of date. So I opened an issue to help start getting it updated. https://drupal.org/node/2256593

There's also some change notices that might be useful in figuring out how this all works now in D8.

Another thing you could try is taking a look at how other module's in Drupal core are using this.

Thanks for the heads up Randy! I've made the video public for now (not sure how it got set to private). We do have a note at the top of this post explaining that this is outdated material, but yes we could probably remove the video and update the blog post so it doesn't read oddly without the video there.

Hi I am using your module but its not showing on configurtion .can you help me pls

Hi sumiti,

Sadly this code is 3 years old and quite out of date. I'll make sure we add a note and get this post in the queue for revision.

In the meantime I'd recommend taking a look at Drupal Console (https://drupalconsole.com/) to help generate forms, and especially the Examples module (https://www.drupal.org/project/examples). In particular the FAPI example submodule (http://cgit.drupalcode.org/examples/tree/fapi_example) has a bunch of well commented code that can help you get started with the form api.

Hi, Please somebody help me. I have a taxonomy with 3 level, and I need create a form field (form['field_x'] = array('type' = 'select', ...)) as shs hierarchical simple select widget. How do that?
Like country and city.

thanks.

About us

Drupalize.Me is the best resource for learning Drupal online. We have an extensive library covering multiple versions of Drupal and we are the most accurate and up-to-date Drupal resource. Learn more