Validation from the last lesson decides whether a submission is allowed through. This lesson covers what happens once it is: attaching your own submit handler to a form you don't own, so your module can react — send an email, log an entry, redirect somewhere custom — the moment that specific form succeeds, without touching the form's original code at all.
What you'll learn in this lesson
- How
#submitworks as an appendable array, just like#validate - The exact signature a submit handler function must have
- Where your added handler runs relative to the form's own original submit logic
The pattern
/**
* Implements hook_form_FORM_ID_alter() for user_register_form.
*/
function mymodule_form_user_register_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
$form['#submit'][] = 'mymodule_registration_submit';
}
/**
* Custom submit handler for the registration form.
*/
function mymodule_registration_submit(array &$form, \Drupal\Core\Form\FormStateInterface $form_state) {
$phone = $form_state->getValue('phone_number');
\Drupal::logger('mymodule')->notice('New registration with phone number: @phone', ['@phone' => $phone]);
}
How it works
Same appending rule as validation
$form['#submit'][] = 'mymodule_registration_submit';
Exactly like #validate from the previous lesson, #submit is a list of callables, and you append to it rather than overwrite it. The form's own class already registered its own submit logic here (typically its own submitForm() method, wired up automatically) — overwrite the array with = instead of [] = and you'd silently disable the form's actual save logic.
The submit handler signature
A submit handler function takes exactly two parameters: the top-level $form array by reference, and $form_state. Unlike a validate callback, there's no third "element" parameter, because a submit handler always operates on the whole submitted form, not one element in isolation.
Execution order matters
See it for yourself
Submit the target form successfully and check /admin/reports/dblog — you should see your custom log entry recorded, proof your appended handler fired after the form's own submission logic completed.
Quick check: could you use this same pattern to change where the user is redirected after submitting someone else's form? (Answer: yes — inside your appended submit handler, call
$form_state->setRedirect('some.route.name'). Since your handler runs after the form's own, your redirect wins over whatever the original handler set.)
Key takeaways
$form['#submit'][]appends a custom submit handler to a form's existing submission pipeline, exactly like#validateand#element_validatebefore it.- A submit handler's signature is fixed:
(array &$form, FormStateInterface $form_state)— no element parameter. - Appended handlers run after the form's own original submit logic, in append order — never prepend unless you specifically need to run before the original.
- Setting a redirect inside an appended handler overrides whatever the original submit logic set, since it runs later.
Coming up next
You've now altered structure, validation, and submission behavior. The last piece is cosmetic but just as common: changing a field's HTML attributes, title, or CSS classes from an alter hook — exactly the kind of small visual tweak you'll reach for constantly.