Processing form data in submit handlersfor Drupal 8 , 9 , 10 , and 11

Last updated :  

Validation was the bouncer checking IDs at the door; submitForm() is what happens once someone's actually inside — the point where a form stops being a UI exercise and starts doing real work: saving a record, sending an email, redirecting somewhere new.

What you'll learn in this lesson

  • How to read submitted values out of $form_state
  • What you can do inside submitForm() — persist data, send a message, redirect
  • Why submitForm() never needs to validate anything itself
  • How to redirect the visitor somewhere after a successful submission

A real submit handler

public function submitForm(array &$form, FormStateInterface $form_state) {
  $title = $form_state->getValue('title');

  // Do the real work — here, just a confirmation message.
  $this->messenger()->addMessage($this->t('You specified a title of %title.', [
    '%title' => $title,
  ]));
}

By the time submitForm() runs, every value has already passed validateForm() — there's no need to re-check anything here. Its only job is reading the now-trusted values with $form_state->getValue('key') and acting on them.

What "acting on them" usually means

  • Saving to a database table or a content/config entity
  • Setting a configuration value with $this->configFactory()->getEditable('your_module.settings')->set('key', $value)->save()
  • Sending an email via the plugin.manager.mail service
  • Showing a confirmation with $this->messenger()->addMessage()
  • Redirecting the visitor with $form_state->setRedirect('route.name')

Redirecting after submission

$form_state->setRedirect('your_module.confirmation_page');

Without an explicit redirect, Drupal reloads the same form after submission — fine for a simple settings form, but often not what you want after, say, creating a new record. setRedirect() takes a route name (plus an optional array of route parameters), sending the visitor somewhere more useful once the work is done.

Common mistake: doing expensive or failure-prone work (an external API call, a large file operation) directly in submitForm() with no error handling. If it's the kind of operation that might legitimately fail after validation has already passed, wrap it in a try/catch and use $this->messenger()->addError() to tell the visitor what went wrong, rather than letting an uncaught exception produce a raw error page.

Quick check: does submitForm() need to check whether the title field is empty, given that validation already ran? No — by the time submitForm() executes, every value in $form_state has already satisfied every check in validateForm(). Re-checking there would be redundant; trust the values.

Key takeaways

  • submitForm() only runs after every check in validateForm() has passed — treat its input as already trusted.
  • $form_state->getValue('key') is how you read a submitted value, using the same key defined in buildForm().
  • Use $form_state->setRedirect('route.name') to send the visitor somewhere other than the same form after submission.
  • Wrap risky operations (external calls, file handling) in try/catch even inside a validated submit handler — validation doesn't guarantee the *outside world* will cooperate.

Coming up next

Every form so far reloads the whole page on submit. Next, we make a form reactive — using AJAX to update part of the page without a full reload.