Skip to main content

Automatically generate forms from config schema

Header image generated by Adobe Firefly AI via the prompt, "Low-contrast but engaging header image for a technical article about automatically generating Drupal forms from typed config schema, including a person working at a keyboard".
An article from ComputerMinds - Building with Drupal in the UK since 2005
2nd Oct 2024

James Williams

Senior Developer
Hey, you seem to look at this article a lot! Why not Bookmark this article so you can find it easily in the future?

Drupal's form API has been brilliant for many years. Still, recently I found myself wondering why I needed to build a configuration form if I already had a schema for my config. Defining a schema facilitates API-first validation (including some pretty smart constraints), specific typing (e.g. actual booleans or integers instead of '0' or '1' strings), and even translation in Drupal. 

That last part got me thinking; if Drupal automatically provides translation forms for typed configuration, why must I build a form? I started diving into the code and found config_translation_config_schema_info_alter() which maps certain config data types to element classes. The ConfigTranslationFormBase::buildForm() class fetches the schema for each config property from the 'config.typed' service (\Drupal\Core\Config\TypedConfigManager) before building the appropriate elements. So Drupal core automatically provides this translation form - notice the long textarea for the 'body' property:

Screenshot of Drupal's config translation UI including different element types
Screenshot of a config translation form from Drupal core

I had built a block plugin that needed some regex-based validation on a couple of its configuration properties. Validation constraints seemed like a natural fit for these, as an inherent part of the property definitions, rather than just on the form level. Drupal has had good support for validation constraints on configuration since version 10.2. This allows forms to be simpler, and config to be fully validatable, even outside the context of a form (e.g. for setting via APIs or config synchronisation). So I defined my config schema like this:

block.settings.mymodule_myblock:
  type: block_settings
  label: 'MyBlock block settings'
  mapping:
    svcid:
      type: string
      label: 'Service ID'
      constraints:
        Regex:
          pattern: '/^[a-zA-Z0-9_\-]+$/'
          message: "The %value can only contain simple letters, numbers, underscores or hyphens."
      default: 'abcde'
      locked: true
    envid:
      type: string
      label: 'Environment ID'
      constraints:
        Regex:
          pattern: '/^[a-zA-Z0-9_\-]+$/'
          message: "The %value can only contain simple letters, numbers, underscores or hyphens."
      default: 'x-j9WsahRe_1An51DhErab-C'

Then I set myself the challenge of building a configuration form 'automatically' from this schema - without using core's config_translation module at all, as this was for a monolingual site. 

I only had two string properties, which meant two textfields, but I wrote the code to use form elements that could be appropriate for other types of property that might get added in the future. The #title of each element could come directly from each label in the schema. (Why do we usually set these in both the schema and form element?!) I added custom default and locked properties to the schema to help encapsulate everything I considered 'inherent' to each part of the config in one place. This meant the configuration form for my block could be fairly simple:

public function blockForm($form, FormStateInterface $form_state) {
  // Each config property will be returned with its schema from $this->getConfigurables().
  foreach ($this->getConfigurables() as $key => $schema_info) {
    $form[$key] = [
      '#type' => match ($schema_info['type']) {
        'string', 'label' => 'textfield',
        'text' => 'textarea',
        'boolean' => 'checkbox',
        'integer', 'float' => 'number',
        'email' => 'email',
      },
      '#title' => $schema_info['label'],
      '#default_value' => $this->configuration[$key],
      '#required' => empty($schema_info['nullable']),
      '#disabled' => $schema_info['locked'] ?? FALSE,
    ];
  }
  return $form;
}

Hopefully that gives an idea of how simple a config form could be - and this could really be reduced further by refactoring it into a generic trait. The code in core's config_translation module for mapping the type of each property to an element type could be much more useful than the fairly naïve match statement above, if it was refactored out to be available even to monolingual sites.

You can explore my full code at https://gist.github.com/anotherjames/bcb7ba55ec56359240b26d322fe2f5a5. That includes the getConfigurables() method which pulls the schema from the TypedConfigManager.

You'll see that I went a little further and picked up the regex constraints for each config property, for use in #pattern form API properties. This provides quick feedback to admins about what characters are allowed using the HTML5 pattern attribute:

Screenshot of browser indicating the form element input is invalid

Not all configuration constraints could be built into the form level. It's arguable that since the Regex constraint and HTML pattern attribute support slightly different regular expression features, this particular one shouldn't be included in a generic trait. Then again, the Choice constraint could be especially useful to include, as it could be used to populate #options for select, radios, or checkboxes elements. We've started using backed Enums with labels for fixed sets of options. Can we wire those up to choice constraints together, I wonder?

Whereas my example was for a configurable plugin's form (which I don't believe can use #config_target), Joachim Noreiko (joachim) has submitted a feature request to Drupal core for forms extending ConfigFormBase to get automatically built from schema. This idea of generating form elements from config schema is still in its infancy, so its limits and benefits need to be explored further. Please let us know in a comment here, or in Joachim's feature request, if you have done anything similar, or have ideas or concerns to point out!

Hi, thanks for reading

ComputerMinds are the UK’s Drupal specialists with offices in Bristol and Coventry. We offer a range of Drupal services including Consultancy, Development, Training and Support. Whatever your Drupal problem, we can help.