Defining Multiple Submit Handlers for Form Buttonsfor Drupal 8 , 9 , 10 , and 11

Last updated :  

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 #submit overrides the form's default submitForm()
  • 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.

Gotcha: setting #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 #submit property of its own, which method handles it when clicked? The form class's own submitForm() — the button-specific override is opt-in, not the default behavior.

Key takeaways

  • Add #submit to an individual button element to give it its own dedicated handler method.
  • A button-specific #submit replaces the class's default submitForm() 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 #submit array 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.