Drupal Form API: A Complete Guide
Drupal’s Form API (FAPI) stands out as one of its most robust features. Whether you're creating a straightforward contact form or a complex multi-step workflow, the Form API equips developers with everything they need to build, validate, and manage forms in a well-organized and secure manner.
What is the Form API?
The Form API is the backbone of Drupal when it comes to creating and managing forms. Rather than just slapping together some raw HTML, you get to define your forms using organized form arrays. These arrays guide Drupal on which fields to show, how to validate them, and what to do with the data once it’s submitted.
Key Features of Form API
Structured Form Arrays - Define form fields, labels, and attributes.
Security Built-In - Automatic CSRF protection and input sanitization.
Validation & Submission Handlers - Custom logic for form processing.
Theming Integration - Forms automatically integrate with Drupal’s theme layer.
AJAX Support - Seamless interactivity with partial page reloads.
Anatomy of a Form in Drupal
A form in Drupal typically includes:
Form Array - Defines the form structure.
Validation Handler - Ensures inputs are correct.
Submit Handler - Processes data after successful validation.
Example 1: Simple Custom Form
Step 1: Create a Form Class
In your module, add: src/Form/CustomForm.php
namespace Drupal\mymodule\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
class CustomForm extends FormBase {
public function getFormId() {
return 'mymodule_custom_form';
}
public function buildForm(array $form, FormStateInterface $form_state) {
$form['name'] = [
'#type' => 'textfield',
'#title' => $this->t('Your Name'),
'#required' => TRUE,
];
$form['email'] = [
'#type' => 'email',
'#title' => $this->t('Your Email'),
'#required' => TRUE,
];
$form['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Submit'),
];
return $form;
}
public function validateForm(array &$form, FormStateInterface $form_state) {
if (strlen($form_state->getValue('name')) < 3) {
$form_state->setErrorByName('name', $this->t('Name must be at least 3 characters.'));
}
}
public function submitForm(array &$form, FormStateInterface $form_state) {
\Drupal::messenger()->addMessage($this->t('Thank you @name!', ['@name' => $form_state->getValue('name')]));
}
}Step 2: Add a Route
In mymodule.routing.yml:
mymodule.custom_form:
path: '/custom-form'
defaults:
_form: '\Drupal\mymodule\Form\CustomForm'
_title: 'Custom Form'
requirements:
_permission: 'access content'Example 2: Form with AJAX
Adding AJAX improves user experience:
$form['dynamic_text'] = [
'#type' => 'textfield',
'#title' => $this->t('Type something'),
'#ajax' => [
'callback' => '::updatePreview',
'wrapper' => 'preview-wrapper',
],
];
$form['preview'] = [
'#type' => 'markup',
'#markup' => '<div id="preview-wrapper"></div>',
];
public function updatePreview(array &$form, FormStateInterface $form_state) {
$text = $form_state->getValue('dynamic_text');
$form['preview']['#markup'] = '<div id="preview-wrapper">Preview: ' . $text . '</div>';
return $form['preview'];
}Example 3: Altering Existing Forms
You can modify existing forms using hook_form_alter():
function mymodule_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
if ($form_id === 'user_login_form') {
$form['custom_message'] = [
'#markup' => '<p>Welcome! Please log in to continue.</p>',
];
}
}Form API Best Practices
Always use
$this->t()for translatable text.Use proper validation handlers to sanitize input.
Attach libraries for styling and JS using
#attached.Rely on FormStateInterface to pass data between handlers.
Use AJAX sparingly and test across different devices.
Drupal’s Form API is essential for creating interactive and secure applications. Whether you’re working on simple forms, enhancing user engagement with AJAX, or tweaking existing forms, getting a handle on FAPI will make your Drupal projects more efficient, user-friendly, and secure.