Configuration import errors in Drupal 8

Overview

Depending on your site installation and configuration import workflow, you may occasionally see errors like this when importing configuration:

The import failed due for [sic] the following reasons: Entities exist of type (whatever). These entities need to be deleted before importing.

Needless to say, this isn't the most intuitive error message. Here's what's happening.

What the error means

Drupal stores a UUID with every configuration object on your site. Typically, there are two copies of each of these objects: one in active storage (i.e. the database), and one on disk (i.e. in config/default). Essentially, this error occurs when the copy of a config object in active storage has a UUID that doesn't match the UUID of the version on disk (i.e. the version being imported).

Why is it a problem if the UUIDs are different? Well, Drupal uses the UUID to track whether a configuration object (such as a field or entity definition) is new or not. For instance, imagine the scenario where you have a configuration entity (say a field), and you delete and recreate that field with the exact same values as the original and then re-export your site's configuration. How is Drupal supposed to know that you actually deleted and recreated the field, if all of its properties are the same? The answer is the UUID, which will be different in the new field.

The final piece of the puzzle to understand here is that Drupal will not allow you to delete types of configuration like content types and vocabularies when content exists for those types.

So what's happening with the configuration import to cause the error is that Drupal is detecting the new UUID during import, and trying to delete and recreate the configuration entity accordingly, but its failing because of the existing content.

Common causes

Most commonly, you will see this error either when running configuration imports as part of a site install, or running configuration imports on a site with existing content. The second scenario is fairly easy to understand given the explanation above: Drupal won't let you delete configuration types with existing content. If you intended to delete and recreate the configuration in question, you will need to write an update hook to migrate existing content to the new config type instead of just relying on config-import to update the content and config. If you didn't intend to delete and recreate the config object, then you simply need to change the UUID of the config object on disk to match the UUID in the database.

The trickier source of this error is on site installs. This can occur when using an installation profile like Standard or Lightning that provides default content types and vocabularies as Features, or creates them programmatically. The problem here is that when Drupal creates a field from scratch (rather than importing from existing config), it assigns it a random UUID. If you've also exported configuration for your site (i.e. to /config/default) and attempt to import that as part of the site install (as with Drush's --config-dir argument), Drupal will see that the UUIDs for all of these configuration objects have changed, and will attempt to delete and recreate them. Not only is this incredibly inefficient, but it can cause problems if you are also creating demo content as part of the install process.

There have been a few solutions to this problem over the last year or so. This Lightning blog post on how to use Config Installer is the most update-to-date summary and best practice.

Other UUID errors

One other type of UUID-related problem commonly occurs that is only tangentially related to this. So far, this post has only dealt with UUIDs associated with config object such as fields and entities. However, the entire Drupal site also has a UUID associated with it that is stored in system.site.yml. Whenever you import configuration, Drupal checks that the site UUID in active storage matches whatever you are importing, and aborts the import if they differ. This is fairly simple to work around: you just need to make sure that the site UUID (system.site:uuid) in active storage matches that in system.site.yml on disk. Drush used to handle this automatically via the --config-dir argument. As of Drush 9, this argument is deprecated, but BLT 9 has similar functionality that automatically sets the site UUID at install time.