Configuration Pages in Drupal 8

Creating New Configuration Category and Settings Pages

I was working on a module the other day, and needed to create a config/settings page. More so, I wanted to add a brand new category to the configuration page, and add my new config/settings page to that category. I found many blog posts that told me how to add a page to an already-existing category (such as /admin/config/content/my-new-page), but none that told me how to add a page properly to a new category (i.e. /admin/config/my-new-category/my-new-page). (I realize I may not be using the proper term of "category", and if someone would like to correct me, I'll thank them.)

Like most things in Drupal 8, it's incredibly easy to do, but you first have to figure out exactly how to do it. I grep'd core and looked for a random already-existing category "regional and language", and took the direction from there.

I'm currently working on building out a module that connects to different City APIs for parking ticket data/etc., so that will explain the nomenclature.

The files I'll need to add/edit are city_connect.links.menu.yml, city_connect.routing.yml and CityConnectAPIsForm.php

If you're not familiar with how routing works now in Drupal 8 with YAML files, you may want to read up on them.

city_connect.links.menu.yml

city_connect.config:
  route_name: city_connect.config
  title: 'City Connect'
  parent: system.admin_config
  description: 'Configure regional settings, localization, and translation.'
  weight: -5

city_connect.apis.config:
  title: 'City APIs'
  parent: city_connect.config
  description: 'Configure API Keys and such'
  route_name: city_connect.apis.config
  • city_connect.config will be the link/routing for the new category. The most important part is the parent setting. This is going to put the link where we want it to fall in the admin/config/ menu
  • cit_connect.apis.config is the actual, usable page/form where we make our config/settings changes. As you can see, I've told it that it's parent is the city_connect.config above.

city_connect.routing.yml

city_connect.config:
  path: '/admin/config/city_connect'
  defaults:
    _controller: '\Drupal\system\Controller\SystemController::systemAdminMenuBlockPage'
    _title: 'City Connect'
  requirements:
    _permission: 'administer city connect'

city_connect.apis.config:
  path: '/admin/config/city_connect/city_apis'
  defaults:
    _form: '\Drupal\city_connect\Controller\CityConnectAPIsForm'
    _title: 'Configure City APIs'
  requirements:
    _permission: 'administer city connect'

If you're not familiar with the way Drupal 8 routing works now with YAML files, the important thing is the _controller value. This is also where we define the path that the links are going to go to/be served at.

  • Notice that in city_connect.config the controller class for our new category is something entirely outside of our own module. \Drupal\system\Controller\SystemController::systemAdminMenuBlockPage is kind of what makes this whole thing work. It's going to be what sets this up in the configuration flow of the site, and lets the pages/forms that mark this as a parent be displayed all nice and neat on the config page.
  • In city_connect.apis.config we send it back a _form instead of a controller; don't worry, it's cool. But we also need to define that form class...

CityConnectAPIsForm

<?php

namespace Drupal\city_connect\Controller;

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

class CityConnectAPIsForm extends ConfigFormBase {

  /**
   * Gets the configuration names that will be editable.
   *
   * @return array
   *   An array of configuration object names that are editable if called in
   *   conjunction with the trait's config() method.
   */
  protected function getEditableConfigNames() {
    return ['city_connect.settings'];
  }

  /**
   * Returns a unique string identifying the form.
   *
   * @return string
   *   The unique string identifying the form.
   */
  public function getFormId() {
    return 'city_connect_settings';
  }

  public function buildForm(array $form, FormStateInterface $form_state) {
    $config = $this->config('city_connect.settings');

    $form['city_baltimore'] = array(
      '#type' => 'fieldset',
      '#title' => t('Baltimore City'),
      '#collapsible' => TRUE,
      '#collapsed' => FALSE,
    );
    $form['city_baltimore']['enable_baltimore'] = array(
      '#type' => 'checkbox',
      '#title' => t('Enable Baltimore City API'),
      '#default_value' => $config->get('enable_baltimore'),
    );
    $form['city_baltimore']['baltimore_app_key'] = array(
      '#type' => 'textfield',
      '#title' => $this->t('Baltimore App Key'),
      '#description' => $this->t('Don\'t have one yet? Go to https://data.baltimorecity.gov/ and register an app to get a key.'),
      '#default_value' => $config->get('baltimore_app_key'),
      '#states' => array(
        'invisible' => array(
          ':input[name="enable_baltimore"]' => array('checked' => FALSE),
        ),
      ),
    );

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

  public function submitForm(array &$form, FormStateInterface $form_state) {
    // Retrieve the configuration
    $this->config('city_connect.settings')
      // Set the submitted configuration setting
      ->set('baltimore_app_key', $form_state->getValue('baltimore_app_key'))
      ->set('enable_baltimore', $form_state->getValue('enable_baltimore'))
      ->save();

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

  public function validateForm(array &$form, FormStateInterface $form_state) {
    parent::validateForm($form, $form_state); // TODO: Change the autogenerated stub
  }
}

Since this is only meant to be about adding to the config pages, and not a lesson on creating the forms themselves, I'm only going to point out the things that I feel could trip you up.

  • There was a change at some point in Drupal 8, and config form data is now immutable. To get around that, you'll need to implement the getEditableConfigNames() function. If you look where I declare it, then down below in the buildForm() function, I'm giving that config the label of city_connect.settings
  • If you're at all familiar with building forms in Drupal 7, the rest should seem familiar; there's clearly just a bit of other OOPiness to it. As well as "variable_get()" and "variable_set()" being pretty much dead.

The Wrap-Up

That's pretty much it. As you can see below, our new category and config/settings form is now available. Like most things in Drupal 8, it's insanely easy to do... once you know how to do it.

Missing media item.
If you don't use the admin_toolbar and admin_toolbar_extras modules, you really should.
Missing media item.
A new category!

 

Missing media item.
And a form.