Using Submit and Button Elements, and Multiple Submit Handlersfor Drupal 11 , 10 , 9 , and 8

Last updated :  

Every form you've built in this topic so far has ended with a submit button, without a real explanation of what makes it different from an ordinary button. This lesson closes out the element catalog with the one that actually finishes the form — and the subtler cousin that doesn't.

What you'll learn in this lesson

  • The submit element and how it triggers the form's submit handlers
  • Why a plain button does not submit the form by default
  • Attaching a dedicated #submit handler to one specific button

A basic submit button

$form['actions']['submit'] = [
  '#type' => 'submit',
  '#value' => $this->t('Save'),
];

Notice #value here, not #title — buttons use #value for their visible label, a naming quirk carried over from raw HTML's <input type="submit" value="...">>. Placing it inside a nested ['actions'] key is the standard Drupal convention — it lets the theme layer style all of a form's action buttons together as a distinct group.

button versus submit: a real difference, not just a name

A #type => 'button' element is genuinely different in behavior, not just cosmetically: without an explicit #ajax property attached, clicking it does nothing at all — no form submission, no page action. It exists specifically as the base for AJAX-triggered interactions (which you'll cover in the AJAX topic later in this course) — never use plain button expecting it to behave like submit.

Giving one button its own submit handler

$form['actions']['save_draft'] = [
  '#type' => 'submit',
  '#value' => $this->t('Save as draft'),
  '#submit' => ['::saveDraftSubmit'],
];

$form['actions']['publish'] = [
  '#type' => 'submit',
  '#value' => $this->t('Publish'),
];

A submit button with its own #submit array runs only that callback instead of the form's default submitForm() — this is exactly how a form with multiple distinct actions (save draft, publish, delete) routes each button to its own logic, all sharing the same set of fields and validation.

The #submit property replaces the default, it doesn't add to it. If you also need the form's normal submitForm() logic to run for that specific button, call it explicitly from inside your custom handler, or list both callbacks in the #submit array — a button with #submit set never falls back to submitForm() automatically.

Quick check: you add a #type => 'button' to a form with no #ajax property, expecting it to submit the form like a regular submit button. What actually happens when a user clicks it? (Nothing — a plain button with no #ajax attached does not submit the form at all. Use #type => 'submit' for a genuine form submission, and reserve plain button for AJAX-triggered behavior.)

Key takeaways

  • Button elements use #value for their visible label, not #title — a legacy naming quirk from raw HTML.
  • #type => 'button' does nothing on click without an attached #ajax property — it does not submit the form like submit does.
  • A submit button with its own #submit array runs only that callback, replacing (not adding to) the form's default submitForm().

Coming up next

You've now covered every element type you'll need for building real forms. The next topic moves from individual field definitions to configuring how those fields behave together — default values, weights, required states, and grouping.