Exploring Drupal's Form API and its role in form creationfor Drupal 11 , 10 , 9 , and 8

Last updated :  

Think of Form API as Drupal's own form-builder factory: instead of hand-writing HTML <form> tags and manually wiring up validation and submission logic, you describe a form as a plain PHP array, and Drupal's Form API assembles the actual HTML, handles CSRF protection, manages multi-step state, and routes submitted values back to your code — all from that one array description.

What you'll learn in this lesson

  • Why Form API exists instead of just writing raw HTML forms
  • The three-method contract every form class implements
  • What Form API handles for you automatically, invisibly

Why not just write HTML?

A hand-written HTML form has to separately handle: CSRF token generation and validation, preserving submitted values on a validation error, multi-step state across requests, AJAX partial updates, and consistent theming across every form on the site. Form API solves all of this once, centrally — your job becomes describing what fields exist, not re-solving each of those problems for every form you write.

The three-method contract

namespace Drupal\form_api_example\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;

class SimpleForm extends FormBase {

  public function getFormId() {
    return 'form_api_example_simple_form';
  }

  public function buildForm(array $form, FormStateInterface $form_state) {
    $form['name'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Your name'),
    ];
    $form['submit'] = [
      '#type' => 'submit',
      '#value' => $this->t('Submit'),
    ];
    return $form;
  }

  public function submitForm(array &$form, FormStateInterface $form_state) {
    $this->messenger()->addMessage($this->t('Hello, @name', [
      '@name' => $form_state->getValue('name'),
    ]));
  }
}

This is the real, minimal shape of a Form API form, taken directly from the Examples module's SimpleForm.php. Three methods, each with one job: getFormId() gives the form a unique machine name, buildForm() returns the array describing every field, and submitForm() runs after a successful submission.

What's happening invisibly: that array from buildForm() gets a hidden CSRF token field, a form_id hidden field, and form-build-id tracking automatically added by Drupal before rendering — none of which you wrote, all of which protects against cross-site request forgery and lets Drupal reconstruct exactly which form and which build submitted, even across an AJAX round trip.

Quick check: why does a Form API form need a getFormId() method at all — why can't Drupal just use the PHP class name? If you said because the form ID is what appears in HTML (in the hidden form_id field) and what other code targets with hooks like hook_form_FORM_ID_alter(), and a class name isn't guaranteed unique or stable across refactors the same way, you've understood the real reason it's explicit.

Key takeaways

  • Form API describes a form as a PHP array — Drupal handles HTML generation, CSRF protection, and state management from that description.
  • Every form class implements getFormId(), buildForm(), and submitForm() at minimum.
  • CSRF tokens and form-build tracking get added automatically — you never write that part yourself.
  • The form ID (not the class name) is what hooks and other code target to identify a specific form.

Coming up next

You've seen the shape of a complete form. Next, we build one from scratch, starting with the base class every form extends.