Saving Configuration Datafor Drupal 8 , 9 , 10 , and 11

Last updated :  

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 in submitForm() instead of reusing the read-only copy from buildForm()
  • What config/install/*.yml is for, and how it relates to what submitForm() 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).

Config vs. State, a distinction worth locking in now: if a value is something a site builder chooses and would want exported/deployed to another environment (like this message), it belongs in Config, exactly as shown here. If it's an environment-specific fact that shouldn't travel between environments (a "last run" timestamp, for example), it belongs in the State API instead — a separate system you'll meet in a later topic.

Quick check: if you deployed this module to a new site where config/install/config_simple_example.settings.yml says message: '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 in buildForm().
  • 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/*.yml provides 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.