Saving Configuration Data from a Settings Formfor Drupal 8 , 9 , 10 , and 11

Last updated :  

Building the form and typing the config are only useful once something actually writes a submitted value back to disk. That's submitForm()'s entire job on a config form — read what the visitor typed out of $form_state, and hand it to the config object's own fluent set()/save() chain. It's a strict, deliberate, three-line pattern, and every config form in Drupal follows it.

What you'll learn in this lesson

  • The exact config()->set()->save() chain every config form's submitForm() follows
  • Why you load the config object as editable, not just readable, inside submitForm()
  • Why you must call parent::submitForm() at the end
  • What actually happens on disk when save() runs

A real submitForm() implementation

/**
 * {@inheritdoc}
 */
public function submitForm(array &$form, FormStateInterface $form_state) {
  // Retrieve the configuration.
  $this->config('config_simple_example.settings')
    // Set the submitted configuration setting.
    ->set('message', $form_state->getValue('message'))
    ->save();

  parent::submitForm($form, $form_state);
}

How it works

One fluent chain: config, set, save

$this->config('config_simple_example.settings')
  ->set('message', $form_state->getValue('message'))
  ->save();

$this->config() (a helper from ConfigFormBase) loads the config object in editable mode. ->set('message', ...) stages the new value in memory — it does not touch disk yet. ->save() is what actually writes the change, both to the active config storage and, if you have a config-sync directory checked out, makes it visible to a future drush config:export.

Reading the submitted value from $form_state

$form_state->getValue('message')

This is the exact same value-reading pattern from any other Form API form — nothing config-specific about it. $form_state->getValue('message') returns whatever the visitor typed into the message textarea, already validated by the time submitForm() runs (validation always happens first, and Drupal never calls submitForm() if validateForm() set an error).

Why parent::submitForm() matters here too

Just like buildForm(), the parent class's submitForm() does real work: it's what shows the "The configuration options have been saved" status message. Skip it, and your save genuinely happens — the config is saved correctly — but the visitor gets no confirmation that anything happened at all.

The chain is fluent by design. set() returns the config object itself (not the saved value), which is exactly what lets you chain multiple set() calls before one final save() — useful when a form saves several keys at once. Calling save() after every individual set() works but writes to disk repeatedly instead of once.

Quick check: if this form saved two fields — message and enabled — would you need two separate save() calls? (Answer: no — chain both set() calls onto the same config object and call save() once at the end: $this->config(...)->set('message', ...)->set('enabled', ...)->save();. One save() per submission, no matter how many keys it touches.)

Key takeaways

  • Config forms always follow the same three-step pattern: $this->config($name)->set($key, $value)->save().
  • set() only stages the change in memory; save() is what actually writes it to config storage.
  • Always call parent::submitForm() — it's what displays the "configuration saved" status message to the visitor.
  • Chain multiple set() calls before a single save() when a form writes more than one key, rather than saving repeatedly.

Coming up next

Saving is only half the story — the next lesson, "Retrieving Configuration Data," looks more closely at the read side: exactly how and where buildForm() pulls that saved value back out to populate the form the next time someone visits it.