Extending ConfigFormBase for Module Settings Formsfor Drupal 8 , 9 , 10 , and 11

Last updated :  

Every settings page under Admin → Configuration — the ones with a single "Save configuration" button at the bottom — is built from the same base class: ConfigFormBase. Think of it like a hotel room's thermostat: the room (your module) has one saved setting (the temperature), and the thermostat (your form) is just a UI for reading and writing that one saved value. This lesson builds the thermostat.

What you'll learn in this lesson

  • Why ConfigFormBase exists and what it gives you for free over a plain FormBase
  • The one method every config form must implement: getEditableConfigNames()
  • How buildForm() reads the current config value into the form as a default
  • Why you must always call parent::buildForm() and parent::submitForm()

A real config settings form

<?php

namespace Drupal\config_simple_example\Form;

use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;

/**
 * Configure example settings for this site.
 */
class ConfigSimpleExampleSettingsForm extends ConfigFormBase {

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'config_simple_example_settings';
  }

  /**
   * {@inheritdoc}
   */
  protected function getEditableConfigNames() {
    return [
      'config_simple_example.settings',
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $config = $this->config('config_simple_example.settings');

    $form['message'] = [
      '#type' => 'textarea',
      '#title' => $this->t('Message'),
      '#default_value' => $config->get('message'),
    ];

    return parent::buildForm($form, $form_state);
  }

}

How it works

Extending ConfigFormBase, not FormBase

ConfigFormBase is a small but important specialization of FormBase. It automatically adds a "Save configuration" submit button, wires up config-import-mode locking (so config forms are correctly disabled during a site's config sync), and gives you the $this->config() helper method used above. None of that comes for free from plain FormBase — it's specifically for forms whose entire job is reading and writing one or more config objects.

getEditableConfigNames() — the one method you must implement

protected function getEditableConfigNames() {
  return [
    'config_simple_example.settings',
  ];
}

This is the one abstract method ConfigFormBase requires. It returns the list of config object names this form is allowed to edit — here, just one: config_simple_example.settings, matching the file config/install/config_simple_example.settings.yml that ships the default value. Drupal uses this list to correctly lock the form during config synchronization, and as a declaration of exactly what this form touches — nothing more.

Reading the current value as a default

$config = $this->config('config_simple_example.settings');

$form['message'] = [
  '#type' => 'textarea',
  '#title' => $this->t('Message'),
  '#default_value' => $config->get('message'),
];

$this->config('config_simple_example.settings') loads the config object, and ->get('message') reads one key from it — exactly the pattern you'd use anywhere else in Drupal to read config, not something special to forms. Whatever is currently saved becomes the textarea's starting value, so the form always reflects reality rather than always showing blank.

Don't forget parent::buildForm(). The base class's buildForm() is what actually adds the "Save configuration" button and the config-locked-during-import warning. Skip that call and you'll have a form with fields but no way to submit it.

Quick check: if you wanted this form to manage two separate config objects instead of one, what would you change? (Answer: add the second config object's name to the array returned by getEditableConfigNames() — the method returns a list specifically so one form can legitimately touch more than one config object.)

Key takeaways

  • Extend ConfigFormBase, not plain FormBase, for any form whose job is editing saved configuration.
  • getEditableConfigNames() is the one required method — it declares exactly which config objects this form may write to.
  • Use $this->config('name')->get('key') to read the current value into #default_value, so the form always shows what's actually saved.
  • Always call parent::buildForm() — it's what adds the submit button and config-import locking behavior.

Coming up next

You've built the form, but nothing here declares what shape message is allowed to be — is it a string? Translatable? That's the config schema's job, and it's what the next lesson, "Defining Config Schema," covers.