Last lesson built the dashboard — a form that reads and displays the current setting. But a dashboard with no wiring behind it doesn't actually change anything; turning the dial has to move something real. That "something real" for a config form is submitForm(), and the whole reason it's worth understanding well is that saving configuration is genuinely different from saving a node: there's no database row to write, no entity to create — you're editing a structured value that lives in a single YAML-backed object, shared site-wide.
What you'll learn in this lesson
- How
submitForm()persists a value into a config object - Why you call
$this->config()again insubmitForm()instead of reusing the read-only copy frombuildForm() - What
config/install/*.ymlis for, and how it relates to whatsubmitForm()writes - Why calling
parent::submitForm()at the end matters
Implementing submitForm()
/**
* {@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);
}
This is the real submitForm() from Drupal's own config_simple_example module, and it's worth reading slowly because every piece is doing something specific:
A fresh, mutable config object
Calling $this->config('config_simple_example.settings') here looks identical to the call in buildForm(), but the object it returns is used differently: in buildForm() you only ever called ->get() on it, whereas here you call ->set(). Both come from the exact same ImmutableConfig-backed helper, but chaining ->set() followed by ->save() is what actually writes the new value to the active configuration storage (the database, on most sites, though it's exportable to YAML via drush cex).
$form_state->getValue('message') — reading what the user actually typed
->set('message', $form_state->getValue('message'))
This is the same $form_state mechanism every Form API form uses. The key 'message' matches the form element's key from buildForm() exactly — Drupal doesn't connect them by magic, it's simply the same array key on both sides.
Never forget parent::submitForm()
ConfigFormBase::submitForm() (the parent method) is what shows the standard "The configuration options have been saved." status message. Skip the parent::submitForm($form, $form_state) call and your save still works — the config value really is written — but the administrator gets no visible confirmation that anything happened, which reads as broken even though it isn't.
Where the default value comes from: config/install
# config_simple_example.settings.yml
message: 'Hello, Drupal!'
This file, sitting in your module's config/install/ directory, is what Drupal copies into active configuration the moment the module is installed — it's why the settings form shows a real value the very first time an administrator opens it, before they've ever saved anything themselves. It uses exactly the same key (message) as the schema and the form, because all three are describing the same piece of data from three different angles: shape (schema), initial value (install YAML), and editing surface (the form).
Quick check: if you deployed this module to a new site where
config/install/config_simple_example.settings.ymlsaysmessage: 'Hello, Drupal!', but an administrator on your dev site already changed it to'Hello, world!'and exported config — what value would a fresh install of the module show? (Answer: whatever the exported active configuration says, not the module's own install default — once a config value has been changed and exported, the site's config sync directory takes precedence over the module's shipped default on reinstall.)
Key takeaways
submitForm()saves by calling->set($key, $value)->save()on a config object loaded via$this->config()— the same helper used to read it inbuildForm().- Match the key you
->set()exactly to the key you read with$form_state->getValue()— they're connected by matching array keys, not automatically. - Always call
parent::submitForm($form, $form_state)— it's what shows the standard save-confirmation message. config/install/*.ymlprovides the config's very first value at module install time; it does not overwrite a value a site has already changed and exported.- Config is for site-builder-chosen, deployable settings; State is for environment-specific runtime facts — don't mix the two.
Coming up next
Saving blindly trusts whatever the administrator typed. Next, you'll add validation to a configuration form so bad input gets caught before it's ever written to config.