41

I'm building a custom module in Drupal 8. It includes some YAML config files.

As I develop I need to change and add to the config, e.g. to add another field onto my custom entity.

At present the only way I've found to get Drupal to notice the changes is to uninstall the module and re-install it.

Is there a way to get Drupal to check that the config files provided by modules is the same as the active config, and if not, update the active config? How are module updates handled? In D7 hook_update_N would be used to add fields using PHP, but it looks like this should be handled by the CM in D8?

Things I've tried after updating the yml files in the module:

  1. drush cr, config sync.

  2. manually copying all the updated config files into sites/default/files/config_XXX/staging/ - but this gives this error "The staged configuration cannot be imported, because it originates from a different site than this site. You can only synchronize configuration between cloned instances of this site.".

  3. manually importing the files one by one using the config manager. This works, but obviously there must be a more automatic way.

  4. [EDIT] manually use the config_update module to inspect changes and 'revert' to the module's config. Again, this is manual.

EDIT: From Managing configuration - do's and don'ts

DON'TS

Try to change the active configuration on your site by changing files in a module's config/install directory. This will NOT work because Drupal will only read from that directory when the module is installed.

...but changes there are going to happen, unless modules are bound to whatever config they needed in their first ever release, and may never ever update or add config.

Thanks in advance.

5
  • I think something very similar was asked before (can't quite find it now), and I think the answer was that default config is only consulted at install time, so re-installing is the way to go. Don't quote me though :)
    – Clive
    Commented Jul 8, 2015 at 9:37
  • 1
    'k, but how would a module get updated? Modules are allowed to get updates in D8, right ;-) ? There must be a way (a la config_update) for modules to say "Drupal! I now require this extra config, take a look and merge it in please." Commented Jul 8, 2015 at 9:41
  • Configuration Update Manager does the job, but I agree it feels like there should be a native way to do this. Something in hook_update_N I'd assume, but I'm not sure what
    – Clive
    Commented Jul 8, 2015 at 9:54
  • 2
    Wow, I think the answer might end up being "you can't"! Never saw that coming! Back to hook_update_N. Excellent article on Drupal 8 for small sites (and part 2). In D8 "sites own their configuration, not modules". Commented Jul 8, 2015 at 10:10
  • I'd like to add that an excellent use-case for this is a multisite setup, where you'd like to share specific large pieces of config but not all and deploy this. These might include custom modules. For a single site it would simply be a config export / import, a multisite would not be that simple.
    – Ambidex
    Commented May 10, 2017 at 14:48

7 Answers 7

33

As mentioned in the original question, and follow-up comments, there are a variety of contrib modules and manual methods to accomplish this.

To do it automatically, or in a custom fashion, I think hook_update_N() is still probably the most viable option.

For instance, this is an example from the Head 2 Head to update system.site to set the default_langcode:

  $config_factory = \Drupal::configFactory();
  $langcode = $config_factory->get('system.site')->get('langcode');
  $config_factory->getEditable('system.site')->set('default_langcode', $langcode)->save();

You can also read in config (recommended only for adding new config, not necessarily updating or overriding config that may have been customized):

  $source = new FileStorage($path);
  /** @var \Drupal\Core\Config\StorageInterface $active_storage */
  $active_storage = \Drupal::service('config.storage');
  $active_storage->write($name, $source->read($name));

where $path is the absolute path to the my_config.foo.yml file.

3
  • 1
    When I am following the second approach the config is written to Drupal but is not getting an UUID even when I export it to the config directory. This lead me into a problem where I tried this with a custom View. The Views overview page returned me an fatal error since the uuid for the Config entity was not available.
    – Sebastian
    Commented Feb 6, 2019 at 16:14
  • @Sebastian you probably had to import the config, e.g: drush cim Commented Mar 30, 2020 at 9:07
  • Option 2 worked great for me. Make sure you are using the correct FileStorage class, i.e. : use Drupal\Core\Config\FileStorage; Commented Oct 20, 2020 at 13:46
20

I've found this Gist on GitHub, which reverts/reloads given module's configuration using drush:

drush cim -y --partial --source=modules/path/to/module/config/install/
0
14

As I landed on this question as well but did not really find the correct answer for my situation here, I'd like to add another answer.

Please note: Anti-pattern ahead!

Use case

When we're developing projects we constantly update our test / acceptance environment with new configuration updates. Take for example a simple fictional News-module, we'd like to add a content-type to the module and deploy this to our acceptance environment. After review, we have concluded there are a few fields missing and other config-related stuff. Since we know the acceptance environment is not being updated in config, we really only want to reload the entire config from the module while added new functionality and not be bothered by importing every changed .yml file.

We only need our config in modules when we're developing multisites. For single sites, we mostly just use exported site config in which the next step is unnecessary .

Reimport config entirely (anti-pattern!)

We found that using the ConfigInstaller service, we're able to reimport the complete config again from a specific module.

// Implement in a update_N hook. 
\Drupal::service('config.installer')->installDefaultConfig('module', $module);

Use with caution!

I'd like to add that this will overwrite any active content that has been altered within the environment. So, only use this solution when you are sure it's safe to overwrite the active config. We'll never use this on a production environment and will only apply in early development.

First try out @jhedstrom's solution before you begin considering this one.

4

Based on my comment: How do I update the configuration of a module?

When I am following the second approach the config is written to Drupal but is not getting an UUID even when I export it to the config directory. This lead me into a problem where I tried this with a custom View. The Views overview page returned me an fatal error since the uuid for the Config entity was not available.

I have created a little function that helps me with that, here my example code:

function _example_views_update_config($configsNames) {
  $config_path    = drupal_get_path('module', 'example') . '/config/install';
  $source         = new FileStorage($config_path);
  $config_storage = \Drupal::service('config.storage');
  $config_factory = \Drupal::configFactory();
  $uuid_service = \Drupal::service('uuid');

  foreach ($configsNames as $name) {
    $config_storage->write($name, $source->read($name));
    $config_factory->getEditable($name)->set('uuid', $uuid_service->generate())->save();
  }
}

/**
 * Add new action configurations.
 */
function example_update_8003() {
  $configsNames = [
    'config-1',
    'config-2',
  ];

  _example_views_update_config($configsNames);
  return 'Added new configurations.';
}
2

The answer above (full re-import) worked for my use case also, but first I spent a little while looking into a more selective re-import. Here's the code I had which seemed to work as an update hook and was based on code in the config_update module:

/**
 * Update all my config.
 *
 * This can be more selective than calling installDefaultConfig().
 */
function MYMODULE_update_8004() {
  $prefixes = [
    'field.storage.node',
    'field.field.node',
    'node.type',
    'core.base_field_override.node',
    'core.entity_view_display'
  ];
  $results = [];
  foreach ($prefixes as $prefix) {
    $results[$prefix] = _update_or_install_config($prefix);
  }
  $return = '';
  foreach ($results as $prefix => $result) {
    $return .= "\n$prefix:\n";
    foreach ($result as $key => $ids) {
      $return .= "$key: " . implode(', ', $ids) . "\n";
    }
  }
  if (function_exists('drush_log')) {
    drush_log($return, \Psr\Log\LogLevel::WARNING);
  }
  return $return;
}


/**
 * Update or install config entities from config/install files.
 *
 * @see \Drupal\config_update\ConfigReverter::import
 * @see \Drupal\config_update\ConfigReverter::revert
 *
 * @param string $prefix
 *   The prefix for YAML files in find, like 'field.storage.node'
 */
function _update_or_install_config($prefix) {
  $updated = [];
  $created = [];
  /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manger */
  $config_manger = \Drupal::service('config.manager');
  $files = glob(__DIR__ . '/config/install/' . $prefix . '.*.yml');
  foreach ($files as $file) {
    $raw = file_get_contents($file);
    $value = \Drupal\Component\Serialization\Yaml::decode($raw);
    if (!is_array($value)) {
      throw new \RuntimeException(sprintf('Invalid YAML file %s'), $file);
    }
    // Lazy hack here since that code ignores the file extension.
    $type = $config_manger->getEntityTypeIdByName(basename($file));
    $entity_manager = $config_manger->getEntityManager();
    $definition = $entity_manager->getDefinition($type);
    $id_key = $definition->getKey('id');
    $id = $value[$id_key];
    /** @var \Drupal\Core\Config\Entity\ConfigEntityStorage $entity_storage */
    $entity_storage = $entity_manager->getStorage($type);
    $entity = $entity_storage->load($id);
    if ($entity) {
      $entity = $entity_storage->updateFromStorageRecord($entity, $value);
      $entity->save();
      $updated[] = $id;
    }
    else {
      $entity = $entity_storage->createFromStorageRecord($value);
      $entity->save();
      $created[] = $id;
    }
  }
  return [
    'updated' => $updated,
    'created' => $created,
  ];
}
1

Configuration Synchronizer module helps to solve this issue in a nice way. This module suite of 7 modules seems to be a bit overkill just for this case (its intention is mainly to safely merge in updates without overwriting customizations), but because of its concept it also allows to track and import configuration changes from module's /install and /optional folders quickly.

Basically, you can test it like following:

  • create and enable your custom module on your local environment with some "default" configuration item(s) placed in /config/install folder as usual
  • install and enable config_sync module and all its dependent modules
  • do some edits in your module's config item inside of /config/install folder
  • access /admin/config/development/configuration/distro . You should see your change and have option to import it into the active configuration (Merge mode is intended to preserve the client changes, Reset mode forces import) - during development I will mostly use Reset mode, but merge mode should work as well unless you did any manual changes in the same configuration in parallel

Note: if you only want to use the config_sync to accelerate config import during module's development (and you don't care about merging in with client updates), it's enough to have this suite installed and enabled on your local (development) environment only (assuming your module will go to higher environments after finalizing and you use the D8 core config management to post its configuration to higher environments).

-1

I discovered this post after trying to add some new config to an existing custom module and drush cex wasn't picking it up. I tried the accepted answer using hook_update_N() but still no luck.

Turns out the project has https://www.drupal.org/project/config_ignore enabled, with the relevant *.settings.yml ignored at /admin/config/development/configuration/ignore

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.