Series Overview & ToC | Previous Article | Next Article - coming soon!


In the previous article, we migrated field storage and instance settings. Along the way, we used the Migrate Skip Fields module to account for content model changes in the new site. Today, we continue with the next field-related migration: widgets. While doing so, we will find out that new migrations might uncover issues or misconfigurations with already executed migrations.

Before we begin

Familiarity with site building concepts related to fields is assumed. Refer to article 16 for a high level overview.

Drupal themes control the appearance of the website and how user interface elements are presented. In a standard Drupal 10 installation, Olivero is the default theme and Claro is the administration theme. Additionally, the administration theme is used when editing or creating content. Our example Drupal 10 project follows this setup. All of this can be configured from the Apperance page at https://migration-drupal10.ddev.site/admin/appearance Alternatively, you could run the following commands:

ddev drush theme:enable olivero && ddev drush --yes config:set system.theme default olivero
ddev drush theme:enable claro && ddev drush --yes config:set system.theme admin claro
ddev drush --yes config:set --input-format=yaml node.settings use_admin_theme TRUE

Field widgets migrations

A widget determines the form elements used to capture field-related data. Each field type can be supported by one or more field widgets. For example, an entity reference field can use an HTML input with autocomplete functionality or a select list. Configuration for the field can affect which form elements are used. Following the same example, an entity reference field can be configured to use the Check boxes/radio buttons widget. In this case, a cardinality of one renders radio buttons. Check boxes are presented when the cardinality is greater than one or when set to unlimited.

When multiple widgets are available for the same field type, Drupal takes care of standardizing how the data is stored in the database. No matter which field type is used for the entity reference field, they are saved in the database using the same data structure.

We use upgrade_d7_field_instance_widget_settings to migrate field widget settings. Copy it from the reference folder into our custom module and rebuild caches for the migration to be detected.

cd drupal10
cp ref_migrations/migrate_plus.migration.upgrade_d7_field_instance_widget_settings.yml web/modules/custom/tag1_migration/tag1_migration_config/migrations/upgrade_d7_field_instance_widget_settings.yml
ddev drush cache:rebuild

Note that while copying the file, we also changed its name and placed it in a migrations folder inside our custom module. After copying the file, make the following changes:

  • Remove the following keys: uuid, langcode, status, dependencies, cck_plugin_method, and migration_group. Notice that for field migrations, we preserve the field_plugin_method key.
  • Add two migration tags: component_entity_form_display and tag1_configuration.
  • Add key: migrate under the source section.
  • Update the required migration_dependencies to include upgrade_d7_field, upgrade_d7_field_instance, and the migrations that create configuration entities.

In addition to the updates above, remember that the Migrate Skip Fields module is installed on the site. Via hook_migration_plugins_alter, it modifies the field widget migration to prevent some fields being imported. In particular, those excluded by the migrate_skip_fields_by_name setting as explained in the previous article. For field widgets, the module does not act on the migrate_skip_fields_by_type setting. The original migration already accounts for not importing widget data for a field whose storage setting was not migrated. This happens in the field_type_exists pipeline of the process section.

We are going to make a small addition here. The last process plugin in this pipeline is skip_on_empty. We are going to add a message configuration to log when a field is skipped because its storage setting is missing. The logs are added to the migration tables and can be viewed from the command line or the migration messages admin interface.

After the modifications, the upgrade_d7_field_instance_widget_settings.yml file should look like this:

id: upgrade_d7_field_instance_widget_settings
class: Drupal\migrate_drupal\Plugin\migrate\FieldMigration
field_plugin_method: alterFieldWidgetMigration
migration_tags:
 - 'Drupal 7'
 - Configuration
 - component_entity_form_display
 - tag1_configuration
label: 'Field instance widget configuration'
source:
 key: migrate
 plugin: d7_field_instance_per_form_display
 constants:
   form_mode: default
   third_party_settings: {  }
process:
 # @modified
 field_type_exists:
   -
     plugin: migration_lookup
     migration: upgrade_d7_field
     source:
       - field_name
       - entity_type
   -
     plugin: extract
     index:
       - 0
   -
     plugin: skip_on_empty
     method: row
     message: "Field storage configuration does not exist."
 bundle:
   -
     plugin: migration_lookup
     migration: upgrade_d7_field_instance
     source:
       - entity_type
       - bundle
       - field_name
   -
     plugin: extract
     index:
       - 1
 form_mode:
   -
     plugin: get
     source: constants/form_mode
 field_name:
   -
     plugin: get
     source: field_name
 entity_type:
   -
     plugin: get
     source: entity_type
   -
     plugin: static_map
     map:
       field_collection_item: paragraph
       paragraphs_item: paragraph
     bypass: true
 options/weight:
   -
     plugin: get
     source: widget/weight
 widget_type:
   -
     plugin: process_field
     source: type
     method: getFieldWidgetType
 options/type:
   type:
     plugin: static_map
     bypass: true
     source: '@widget_type'
     map:
       link_field: link_default
       email_textfield: email_default
       date_select: datetime_default
       date_text: datetime_default
       date_popup: datetime_default
       media_generic: file_generic
       phone_textfield: telephone_default
       options_onoff: boolean_checkbox
       entityreference_autocomplete: entity_reference_autocomplete
       entityreference_autocomplete_tags: entity_reference_autocomplete_tags
       taxonomy_autocomplete: entity_reference_autocomplete
       d7_text: d7_text_default
       taxonomy_term_reference: taxonomy_term_reference_default
       image: image_default
       image_miw: image_image
       url_external: link_default
       field_collection_embed: entity_reference_paragraphs
       field_collection: field_collection_default
       addressfield_standard: address_default
       entityreference: entityreference_default
       file_mfw: file_generic
       filefield_widget: file_generic
       list: list_default
       date: datetime_default
       datetime: datetime_default
       datestamp: datetime_timestamp
       number_default: number_default_default
 options/settings:
   -
     plugin: field_instance_widget_settings
     source:
       - widget/type
       - widget/settings
 options/third_party_settings:
   -
     plugin: get
     source: constants/third_party_settings
destination:
 plugin: component_entity_form_display
migration_dependencies:
 required:
   - upgrade_d7_field
   - upgrade_d7_field_collection_type
   - upgrade_d7_field_instance
   - upgrade_d7_node_type
   - upgrade_d7_taxonomy_vocabulary
 optional: {  }

Now, rebuild caches for our changes to be detected and execute the migration. Run migrate:status to make sure we can connect to Drupal 7. Then, run migrate:import to perform the import operations.

ddev drush cache:rebuild
ddev drush migrate:status upgrade_d7_field_instance_widget_settings
ddev drush migrate:import upgrade_d7_field_instance_widget_settings

No errors after running the migration. Great! Feel free to have a look at the logs using the drush migrate:messages command.

Similar to the migrations of storage and instance settings, we should review that the result of migrating field widget settings is correct. There are two places you should look at. Using the Article content type as an example, go to:

  1. The manage form display page where you can make adjustments to the migrated field widget configuration.
  2. The add article page where you can see the current state of the field widget configuration.

This is where having familiarity with site building concepts and past experience will help. Below is a screenshot of the Add content page for the Article content type.

Add Content Page

Compare it with the equivalent page in Drupal 7: https://migration-drupal7.ddev.site/node/add/article With regards to fields, there are three things to note:

  1. The Image (field_image) field is missing. This is to be expected because we will be using media reference fields instead. All uses of the field_image field were skipped while working on the storage and instance migrations. We are going to add media reference fields in a future article.
  2. The Tags (field_tags) field does not behave the same compared to the source site. In Drupal 7, there is a single HTML input element that permits entering a comma-separated list of taxonomy terms. Additionally, if a term does not exist, it will be created on the fly in the Article Tags taxonomy vocabulary. In Drupal 10, the field is currently configured to use a separate HTML input element for each term we want to reference. Moreover, creating terms on the fly is not allowed.
  3. The Body (body) field does not offer the option to choose a text format. At the moment only the Plain text format exists on the site. When there are no other formats available, rich text fields do not show the select list for choosing among the available options. We are going to cover migrating text formats in a future article.

Understanding how field widget settings are mapped

The entity reference system received a major overhaul in Drupal 8 and that has carried over ever since. For brevity, we are only going to highlight what is relevant to the example at hand.

In Drupal 7, there were at least two ways to reference taxonomy terms. One is using Term reference fields provided by the core Taxonomy module and the other is using Entity Reference fields provided by the contributed Entity Reference module. The former provides one widget called Autocomplete term widget (tagging). The latter provides two widgets: Autocomplete and Autocomplete (Tags style).

Drupal 10 follows the pattern of the contributed module by providing two widgets for referencing entities and even reuses the same labels. Below are the machine names and labels for all five widgets:

  1. taxonomy_autocomplete: Drupal 7's core taxonomy module Autocomplete term widget (tagging) widget.
  2. entityreference_autocomplete: Drupal 7's contributed entityreference module Autocomplete widget.
  3. entityreference_autocomplete_tags: Drupal 7's contributed entityreference module Autocomplete (Tags style) widget.
  4. entity_reference_autocomplete: Drupal 10 core Autocomplete widget.
  5. entity_reference_autocomplete_tags: Drupal 10 core Autocomplete (Tags style) widget.

Take a look at the following snippet extracted from the upgrade_d7_field_instance_widget_settings migration:

process:
 options/type:
   type:
     plugin: static_map
     bypass: true
     source: '@widget_type'
     map:
       entityreference_autocomplete: entity_reference_autocomplete
       entityreference_autocomplete_tags: entity_reference_autocomplete_tags
       taxonomy_autocomplete: entity_reference_autocomplete

This is where the mapping of widgets settings from source to destination happens. Notice that taxonomy_autocomplete from Drupal 7 is mapped to entity_reference_autocomplete in Drupal 10. This is why the Tags (field_tags) field in Drupal 10 does not offer the tags style behavior. For that to work, we need to update its instance settings to leverage the entity_reference_autocomplete_tags.

As for creating terms on the fly, Drupal 7 Term reference fields could only include terms from a single vocabulary. In Drupal 10, entity reference fields allow including entities from multiple bundles of the same entity type. When multiple bundles can be referenced and creating entities that do not exist is allowed, you need to pick one bundle where the new entities will be added. In the case of the Tags (field_tags) field in Drupal 10, we need to update field instance settings to allow creating taxonomy terms on the fly.

That was quite a technical detour from reviewing the result of the field widget migration. In most cases, you do not need to know the technicalities of how the Migrate API maps configuration from Drupal 7 to Drupal 10. That being said, we have leveled up by gaining a deeper understanding of how the transformation process works.

Manual updates to field widget settings

In your own projects, take the time to review the migrated widget settings for all fields across all entity types. For our example project, below is the list of adjustments that need to be made manually:

  • field_tags field in article content type: change field storage to create referenced entities if they do not already exist and update the widget to use the Autocomplete (Tags style) one.
  • field_topics field in session content type: change field storage to create referenced entities if they do not already exist and update the widget to use the Autocomplete (Tags style) one.
  • field_address field in venue content type: update the field instance settings to make the United States as the only available country and hide some address components. Namely, add an override to hide: First name, Middle name, Last name, Organization, Address line 3.

To replicate the tag style behavior for the field_tags field, go to Manage form display page for the Article content type: https://migration-drupal10.ddev.site/admin/structure/types/manage/article/form-display and select the Autocomplete (Tags style) widgets for the Tags field.

Article tags

To allow creating terms on the fly in field_tags field go to the Manage fields page for the Article content type, go to https://migration-drupal10.ddev.site/admin/structure/types/manage/article/fields Then, edit the Tags field and in the Reference type section check the box for Create referenced entities if they don't already exist.

If we were to allow referencing terms from multiple vocabularies, you will get a prompt labeled Store new items in to indicate which vocabulary should contain the newly created terms. The image below shows this interface for reference, but in our final example, the Tags field only allows referencing terms from one vocabulary: Article Tags.

Manage form display page

The changes to field_topics and field_address will be left as an exercise to the reader. Refer to the code repository for the final results of the migration of configuration.

While reviewing the field widget migration, we identified instance settings issues that required manual updates. This is common in the migration process, where adjustments to previous migrations are necessary when working on new ones. Remember that field migrations are complex, with storage, instance, widget, and formatter settings being closely interconnected.

We’ve covered significant ground in our comprehensive Drupal 7 to 10 migration series to date, but there is more to explore. Our next article will focus on migrating view modes and field groups. We encourage you to apply these insights to your own migration projects and look forward to guiding you through the next steps.


Image by PIRO from Pixabay