A single "Submit" button assumes there's only ever one thing a user might want to do with a form. Real forms are rarely that simple — think of a document editor with separate "Save Draft" and "Publish" buttons. Both submit the same fields, but each needs to run different logic afterward. Drupal handles this with a per-button #submit property, letting one form definition serve several distinct actions.
What you'll learn in this lesson
- How to attach a dedicated submit callback to one specific button
- Why button-specific
#submitoverrides the form's defaultsubmitForm() - How to tell which button was clicked from inside a shared handler
Giving one button its own handler
$form['actions']['save'] = [
'#type' => 'submit',
'#value' => $this->t('Save draft'),
'#submit' => ['::saveDraft'],
];
$form['actions']['publish'] = [
'#type' => 'submit',
'#value' => $this->t('Publish'),
'#submit' => ['::publishNow'],
];
Adding #submit to an individual button element (rather than relying only on the class's own submitForm()) tells Drupal exactly which method to call when that specific button triggers the submission. '::saveDraft' is shorthand for "call the saveDraft() method on this same form class."
The handler methods themselves
public function saveDraft(array &$form, FormStateInterface $form_state) {
$this->messenger()->addMessage($this->t('Draft saved.'));
}
public function publishNow(array &$form, FormStateInterface $form_state) {
$this->messenger()->addMessage($this->t('Published!'));
}
Each method has the exact same signature as the default submitForm() — Drupal calls whichever one matches the button that was actually clicked, with the full, already-validated $form_state available either way.
#submit on a button REPLACES the class's default submitForm() for that button — it doesn't run in addition to it. If you need shared logic to run no matter which button was pressed, call it explicitly from inside each handler, or list multiple callbacks in the #submit array: ['::sharedLogic', '::saveDraft'].Quick check: if a button has no
#submitproperty of its own, which method handles it when clicked? The form class's ownsubmitForm()— the button-specific override is opt-in, not the default behavior.
Key takeaways
- Add
#submitto an individual button element to give it its own dedicated handler method. - A button-specific
#submitreplaces the class's defaultsubmitForm()for that button, rather than running alongside it. - Every submit handler shares the same
(array &$form, FormStateInterface $form_state)signature, so they're interchangeable in terms of what data they can access. - Stack multiple callback names in one
#submitarray to run several handlers in sequence for the same button.
Coming up next
Now that you can route different buttons to different handlers, the next lesson looks at what every submit handler actually does with the data it receives — reading values back out of $form_state.