Managing how data flows from a user's browser into your database is a fundamental skill for any Drupal developer. Whether you are building a custom module or tweaking a core feature, you will eventually need to intercept a form's submission process. Perhaps you need to sanitize input, send a notification to an external API, or modify specific characters before the data is saved. This is where mastering the Drupal form submission handler becomes essential.

In this guide, we will explore the proper way to alter form submission logic without breaking existing functionality. We will cover the differences between Drupal 7 and modern versions like Drupal 10 and 11, ensuring you have the tools to handle forms in any environment.

Understanding the Drupal Form Lifecycle

Before diving into the code, it is important to understand how Drupal processes a form. The Form API (FAPI) follows a specific lifecycle:

  1. Build: The form structure is defined in an associative array.
  2. Validate: Drupal checks if the submitted data meets the requirements (e.g., required fields, correct email format).
  3. Submit: If validation passes, the submission handlers are executed to process the data.

When you want to change what happens after a user clicks "Submit," you are looking to hook into that third stage. However, as we will see, sometimes the "Validation" stage is actually the better place for data manipulation.

How to Properly Add a Custom Submit Handler

A common mistake developers make is overwriting the entire #submit array. In Drupal, the $form['#submit'] element is an array of function names. If you simply assign your function to it, you wipe out all the original handlers, such as the one that saves the node or sends the contact email.

The Wrong Way (Overwriting)

// DO NOT DO THIS
function hook_form_alter(&$form, $form_state, $form_id) {
  $form['#submit'] = 'my_custom_submission_function';
}

The Right Way (Appending or Prepending)

To keep existing functionality intact, you should append your handler to the array. If you need your logic to run before the default handlers, use array_unshift to place it at the beginning of the stack.

For Drupal 7:

/**
 * Implements hook_form_alter().
 */
function mymodule_form_alter(&$form, &$form_state, $form_id) {
  // Prepend your handler to run first
  array_unshift($form['#submit'], 'mymodule_custom_submission');

  // OR Append your handler to run last
  // $form['#submit'][] = 'mymodule_custom_submission';
}

Modern Drupal: Using FormStateInterface

In modern Drupal (8, 9, 10, and 11), the logic remains similar, but we use the FormStateInterface to interact with submitted values. This object-oriented approach is cleaner and more robust.

If you want to alter every single form on your site, you use hook_form_alter. However, it is usually better for performance and stability to target a specific form using hook_form_FORM_ID_alter.

<?php

use Drupal\Core\Form\FormStateInterface;

/**
 * Implements hook_form_FORM_ID_alter().
 * 
 * Replace 'example_form_id' with the actual ID of your form.
 */
function my_module_form_example_form_id_alter(&$form, FormStateInterface &$form_state, $form_id) {
  // Add a custom submit handler to the existing list
  $form['#submit'][] = 'my_module_custom_submit_handler';
}

/**
 * Custom form submit handler.
 */
function my_module_custom_submit_handler(&$form, FormStateInterface &$form_state) {
  $field_name = 'field_text_data';
  $value = $form_state->getValue($field_name);

  if ($value) {
    // Example: Replace specific characters or format the string
    $processed_value = str_replace("bad-char", "good-char", $value);

    // Update the form state with the new value
    $form_state->setValue($field_name, $processed_value);
  }
}

Modifying Data: Submit vs. Validate

You might encounter a scenario where you need to clean up data before it is even considered for submission. While you can modify values in a submit handler, it is often considered a best practice to perform data normalization during the validation phase.

Using $form['#validate'] allows you to modify the $form_state values before any submit handler (including the default ones) touches them. This ensures that the data being saved to the database is already in the desired format.

Example: Data Normalization in Validation

function mymodule_form_alter(&$form, &$form_state, $form_id) {
  // Add a validation handler to clean input
  $form['#validate'][] = 'mymodule_clean_input_validation';
}

function mymodule_clean_input_validation(&$form, FormStateInterface &$form_state) {
  $inbound_text = $form_state->getValue('message');
  $cleaned_text = trim(strip_tags($inbound_text));
  $form_state->setValue('message', $cleaned_text);
}

Targeting Specific Forms vs. Global Changes

When implementing a Drupal form submission handler, you must decide the scope of your changes.

  1. hook_form_alter: Use this if you need to apply logic across multiple different forms (e.g., adding a specific character filter to every text field on the site). Use this sparingly, as it runs on every form load, including the administrative back-end.
  2. hook_form_FORM_ID_alter: This is the preferred method. It only triggers for the specific form you identify, which is better for performance and prevents unintended side effects on other parts of your site.

To find a form's ID, you can inspect the HTML source of the form; look for the <input type="hidden" name="form_id" value="example_form_id"> element.

Frequently Asked Questions

How do I ensure my submit handler runs last?

By default, appending to the array using $form['#submit'][] = 'my_function'; will place your handler at the end of the list. However, if other modules are also altering the form, the order depends on the module weight. You can adjust your module's weight in the system table or use hook_module_implements_alter to ensure your hook runs last.

Can I stop other submit handlers from running?

Yes. If you need to halt the submission process entirely, you can use $form_state->setErrorByName() within a validation handler. If any errors are set during validation, Drupal will not execute any of the submit handlers.

What is the difference between $form_state->getValue() and $form_state->getValues()?

$form_state->getValue('key') retrieves a specific value for a single field, while $form_state->getValues() returns an associative array of all submitted data. For most targeted alterations, getValue() is the safer and more readable choice.

Wrapping Up

Altering a Drupal form submission handler is a powerful way to extend Drupal's functionality. By using hook_form_alter correctly and appending your handlers rather than overwriting them, you ensure that your site remains stable and maintainable. Remember to choose the right stage of the lifecycle—use validation for data cleaning and submission for post-processing actions like external API calls or custom redirects.

Always test your form alterations thoroughly, especially when using global hooks, to ensure that administrative forms and user-facing forms continue to behave as expected.