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'ssubmitForm()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.
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 —
messageandenabled— would you need two separatesave()calls? (Answer: no — chain bothset()calls onto the same config object and callsave()once at the end:$this->config(...)->set('message', ...)->set('enabled', ...)->save();. Onesave()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 singlesave()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.