diff --git a/core/modules/field/migrations/d6_field_formatter_settings.yml b/core/modules/field/migrations/d6_field_formatter_settings.yml
index 9f322d45b1..ea3dd14d31 100644
--- a/core/modules/field/migrations/d6_field_formatter_settings.yml
+++ b/core/modules/field/migrations/d6_field_formatter_settings.yml
@@ -60,14 +60,11 @@ process:
   "options/weight": weight
   "options/type":
     -
-      plugin: static_map
-      bypass: true
+      plugin: field_migration_plugin_id_mapper
       source:
         - type
         - 'display_settings/format'
-      map: { }
-    -
-      plugin: d6_field_type_defaults
+      field_plugin: field_formatter
   "options/settings":
     -
       plugin: static_map
diff --git a/core/modules/field/migrations/d6_field_instance_widget_settings.yml b/core/modules/field/migrations/d6_field_instance_widget_settings.yml
index cb6bb8c524..4cdbcbba3f 100644
--- a/core/modules/field/migrations/d6_field_instance_widget_settings.yml
+++ b/core/modules/field/migrations/d6_field_instance_widget_settings.yml
@@ -41,11 +41,12 @@ process:
   entity_type: 'constants/entity_type'
   'options/weight': weight
   'options/type':
-    type:
-      plugin: static_map
-      bypass: true
-      source: widget_type
-      map: { }
+    -
+      plugin: field_migration_plugin_id_mapper
+      source:
+        - type
+        - widget_type
+      field_plugin: field_widget
   'options/settings':
     -
       plugin: field_instance_widget_settings
diff --git a/core/modules/field/migrations/d7_field_formatter_settings.yml b/core/modules/field/migrations/d7_field_formatter_settings.yml
index 2aa348efdc..c6ed289177 100644
--- a/core/modules/field/migrations/d7_field_formatter_settings.yml
+++ b/core/modules/field/migrations/d7_field_formatter_settings.yml
@@ -61,31 +61,14 @@ process:
   field_name: field_name
   "options/label": 'formatter/label'
   "options/weight": 'formatter/weight'
-  # The field plugin ID.
-  plugin_id:
-    plugin: process_field
-    source: type
-    method: getPluginId
-  # The formatter to use.
-  formatter_type:
-    plugin: process_field
-    source: type
-    method: getFieldFormatterType
   "options/type":
     -
-      plugin: static_map
-      bypass: true
+      plugin: field_migration_plugin_id_mapper
       source:
-        - '@plugin_id'
-        - '@formatter_type'
-      # The map is generated by the getFieldFormatterMap() method from the
-      # migrate field plugins.
-      map: []
-    -
-      plugin: d7_field_type_defaults
-    -
-      plugin: skip_on_empty
-      method: row
+        - type
+        - 'formatter/type'
+      source_field_type: type
+      field_plugin: field_formatter
   hidden:
     plugin: static_map
     source: "@options/type"
diff --git a/core/modules/field/migrations/d7_field_instance_widget_settings.yml b/core/modules/field/migrations/d7_field_instance_widget_settings.yml
index 8fdce6d46c..c371900b13 100644
--- a/core/modules/field/migrations/d7_field_instance_widget_settings.yml
+++ b/core/modules/field/migrations/d7_field_instance_widget_settings.yml
@@ -47,16 +47,14 @@ process:
   field_name: field_name
   entity_type: entity_type
   'options/weight': 'widget/weight'
-  widget_type:
-    plugin: process_field
-    source: type
-    method: getFieldWidgetType
   'options/type':
-    type:
-      plugin: static_map
-      bypass: true
-      source: '@widget_type'
-      map: { }
+    -
+      plugin: field_migration_plugin_id_mapper
+      source:
+        - type
+        - 'widget/type'
+      source_field_type: type
+      field_plugin: field_widget
   'options/settings':
     plugin: field_instance_widget_settings
     source:
diff --git a/core/modules/field/src/Plugin/migrate/process/FieldMigrationPluginIdMapper.php b/core/modules/field/src/Plugin/migrate/process/FieldMigrationPluginIdMapper.php
new file mode 100644
index 0000000000..f58ae0e239
--- /dev/null
+++ b/core/modules/field/src/Plugin/migrate/process/FieldMigrationPluginIdMapper.php
@@ -0,0 +1,181 @@
+<?php
+
+namespace Drupal\field\Plugin\migrate\process;
+
+use Drupal\Component\Plugin\Exception\PluginException;
+use Drupal\Component\Plugin\Exception\PluginNotFoundException;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\migrate\MigrateExecutableInterface;
+use Drupal\migrate\Plugin\MigrationInterface;
+use Drupal\migrate\ProcessPluginBase;
+use Drupal\migrate\Row;
+use Drupal\migrate_drupal\Plugin\MigrateFieldInterface;
+use Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Process plugin for field formatter and widget plugin IDs.
+ *
+ * This process plugin maps field formatter and field widget plugin IDs from
+ * Drupal 6 / 7 sources to the corresponding destination plugin ID using the
+ * migrate field plugin manager while it provides an option to override the
+ * mappings with hook implementation.
+ *
+ * The source value has to be an array, where the first value is the type ID,
+ * and the second value is the formatter / widget plugin ID of the actual field.
+ *
+ * @todo Document the hooks.
+ *
+ * Example:
+ * @code
+ * "options/type":
+ *   plugin: field_migration_plugin_id_mapper
+ *   source:
+ *     - type
+ *     - 'display_settings/format'
+ *   field_plugin: field_formatter[|field_widget]
+ * @endcode
+ *
+ * @MigrateProcessPlugin(
+ *   id = "field_migration_plugin_id_mapper",
+ *   provider = "migrate_drupal"
+ * )
+ */
+class FieldMigrationPluginIdMapper extends ProcessPluginBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The type and map callbacks, grouped by the supported field_plugin config.
+   *
+   * @const array[]
+   */
+  const PROPERTY_MAP = [
+    'field_formatter' => [
+      'plugin_type_callback' => 'getFieldFormatterType',
+      'map_callback' => 'getFieldFormatterMap',
+    ],
+    'field_widget' => [
+      'plugin_type_callback' => 'getFieldWidgetType',
+      'map_callback' => 'getFieldWidgetMap',
+    ],
+  ];
+
+  /**
+   * The migration plugin instance the current process belongs to.
+   *
+   * @var \Drupal\migrate\Plugin\MigrationInterface
+   */
+  protected $migration;
+
+  /**
+   * The migrate field plugin manager.
+   *
+   * @var \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface
+   */
+  protected $migrateFieldPluginManager;
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * Constructs a FieldMigrationPluginIdMapper instance.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin ID for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\migrate\Plugin\MigrationInterface $migration
+   *   The migration entity.
+   * @param \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface $migrate_field_plugin_manager
+   *   The field migration plugin manager.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, $migrate_field_plugin_manager, ModuleHandlerInterface $module_handler) {
+    $property = $configuration['field_plugin'] ?? NULL;
+    if (!is_string($property) || !array_key_exists($property, self::PROPERTY_MAP)) {
+      $valid_configs = array_keys(self::PROPERTY_MAP);
+      $last_valid_config = array_pop($valid_configs);
+      throw new \LogicException(
+        sprintf(
+          "The %s migrate process plugin requires a field_plugin configuration with a value set either to %s or %s",
+          $plugin_id,
+          implode(', ', $valid_configs),
+          $last_valid_config
+        )
+      );
+    }
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->migration = $migration;
+    $this->migrateFieldPluginManager = $migrate_field_plugin_manager;
+    $this->moduleHandler = $module_handler;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $migration,
+      $container->get('plugin.manager.migrate.field'),
+      $container->get('module_handler')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
+    if (!is_array($value) || count($value) !== 2) {
+      throw new \InvalidArgumentException(
+        sprintf(
+          "The %s migrate process plugin must have at least two source values with string type: The first one should be the field type in source, the second should be the source widget or formatter plugin ID",
+          $this->pluginId
+        )
+      );
+    }
+
+    $property = $this->configuration['field_plugin'];
+    [
+      $source_field_type,
+      $source_field_plugin_id,
+    ] = $value;
+    [
+      'plugin_type_callback' => $plugin_type_callback,
+      'map_callback' => $map_callback,
+    ] = self::PROPERTY_MAP[$property];
+
+    if ($this->migrateFieldPluginManager instanceof MigrateFieldPluginManagerInterface) {
+      try {
+        $field_plugin_id = $this->migrateFieldPluginManager->getPluginIdFromFieldType($source_field_type, [], $this->migration);
+        $field_plugin = $this->migrateFieldPluginManager->createInstance($field_plugin_id);
+        assert($field_plugin instanceof MigrateFieldInterface);
+        $prepared_source_plugin_type = $field_plugin->$plugin_type_callback($row);
+        $plugin_id_map = $field_plugin->$map_callback();
+      }
+      catch (PluginNotFoundException $e) {
+        return $value;
+      }
+      catch (PluginException $e) {
+        return $value;
+      }
+    }
+
+    $hook_results = $this->moduleHandler->invokeAll(
+      "field_migration_{$property}_info",
+      [$row, $this->migration]
+    );
+
+    return $hook_results[$source_field_type][$source_field_plugin_id] ?? $plugin_id_map[$prepared_source_plugin_type] ?? $prepared_source_plugin_type;
+  }
+
+}
diff --git a/core/modules/field/tests/modules/field_plugin_migrate_fallback_test/field_plugin_migrate_fallback_test.info.yml b/core/modules/field/tests/modules/field_plugin_migrate_fallback_test/field_plugin_migrate_fallback_test.info.yml
new file mode 100644
index 0000000000..b240298e69
--- /dev/null
+++ b/core/modules/field/tests/modules/field_plugin_migrate_fallback_test/field_plugin_migrate_fallback_test.info.yml
@@ -0,0 +1,4 @@
+name: Field Formatter and Widget Migration Fallback Test
+type: module
+package: Testing
+version: VERSION
diff --git a/core/modules/field/tests/modules/field_plugin_migrate_fallback_test/field_plugin_migrate_fallback_test.module b/core/modules/field/tests/modules/field_plugin_migrate_fallback_test/field_plugin_migrate_fallback_test.module
new file mode 100644
index 0000000000..3133b2dd75
--- /dev/null
+++ b/core/modules/field/tests/modules/field_plugin_migrate_fallback_test/field_plugin_migrate_fallback_test.module
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * @file
+ * Test field plugins for testing formatter and widget fallback.
+ */
+
+use Drupal\field_plugin_migrate_fallback_test\D6TextField;
+use Drupal\field_plugin_migrate_fallback_test\D7TextField;
+
+/**
+ * Implements hook_migrate_field_info_alter().
+ */
+function field_plugin_migrate_fallback_test_migrate_field_info_alter(array &$definitions) {
+  if (!empty($definitions['d7_text'])) {
+    $definitions['d7_text']['class'] = D7TextField::class;
+  }
+  if (!empty($definitions['d6_text'])) {
+    $definitions['d6_text']['class'] = D6TextField::class;
+  }
+}
diff --git a/core/modules/field/tests/modules/field_plugin_migrate_fallback_test/src/D6TextField.php b/core/modules/field/tests/modules/field_plugin_migrate_fallback_test/src/D6TextField.php
new file mode 100644
index 0000000000..c0be51e139
--- /dev/null
+++ b/core/modules/field/tests/modules/field_plugin_migrate_fallback_test/src/D6TextField.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Drupal\field_plugin_migrate_fallback_test;
+
+use Drupal\text\Plugin\migrate\field\d6\TextField;
+
+/**
+ * Replacement class for the Drupal 7 Test field migration plugin for testing.
+ */
+class D6TextField extends TextField {
+
+  use TextFieldTrait;
+
+}
diff --git a/core/modules/field/tests/modules/field_plugin_migrate_fallback_test/src/D7TextField.php b/core/modules/field/tests/modules/field_plugin_migrate_fallback_test/src/D7TextField.php
new file mode 100644
index 0000000000..3b8fe9b1f7
--- /dev/null
+++ b/core/modules/field/tests/modules/field_plugin_migrate_fallback_test/src/D7TextField.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Drupal\field_plugin_migrate_fallback_test;
+
+use Drupal\text\Plugin\migrate\field\d7\TextField;
+
+/**
+ * Replacement class for the Drupal 7 Test field migration plugin for testing.
+ */
+class D7TextField extends TextField {
+
+  use TextFieldTrait;
+
+}
diff --git a/core/modules/field/tests/modules/field_plugin_migrate_fallback_test/src/TextFieldTrait.php b/core/modules/field/tests/modules/field_plugin_migrate_fallback_test/src/TextFieldTrait.php
new file mode 100644
index 0000000000..5c7164d960
--- /dev/null
+++ b/core/modules/field/tests/modules/field_plugin_migrate_fallback_test/src/TextFieldTrait.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\field_plugin_migrate_fallback_test;
+
+use Drupal\migrate\Plugin\MigrationInterface;
+
+/**
+ * Trait for test text field plugins.
+ */
+trait TextFieldTrait {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function alterFieldFormatterMigration(MigrationInterface $migration) {
+    parent::alterFieldFormatterMigration($migration);
+    $migration->mergeProcessOfProperty(
+      'options/settings',
+      ['formatter_fallback_test' => ['plugin' => 'get']],
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function alterFieldWidgetMigration(MigrationInterface $migration) {
+    parent::alterFieldWidgetMigration($migration);
+    $migration->mergeProcessOfProperty(
+      'options/settings',
+      ['widget_fallback_test' => ['plugin' => 'get']],
+    );
+  }
+
+}
diff --git a/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFormatterWidgetFallbackTest.php b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFormatterWidgetFallbackTest.php
new file mode 100644
index 0000000000..23f85fa3c3
--- /dev/null
+++ b/core/modules/field/tests/src/Kernel/Migrate/d6/MigrateFormatterWidgetFallbackTest.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace Drupal\Tests\field\Kernel\Migrate\d6;
+
+use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
+
+/**
+ * Tests formatter and widget fallback functionality.
+ */
+class MigrateFormatterWidgetFallbackTest extends MigrateDrupal6TestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'field_plugin_migrate_fallback_test',
+    'node',
+    'text',
+  ];
+
+  /**
+   * Tests that field formatter alter of field migrate plugins are invoked.
+   *
+   * @see \Drupal\field_plugin_migrate_fallback_test\D7TextField::alterFieldFormatterMigration()
+   */
+  public function testFormatterFallback(): void {
+    $formatter_migration = $this->getMigration('d6_field_formatter_settings');
+    $definition_before = $formatter_migration->getPluginDefinition();
+    $original_settings_process = static::normalizeProcessPipeline($definition_before['process']['options/settings']);
+
+    $formatter_migration->getProcess();
+
+    $definition_after = $formatter_migration->getPluginDefinition();
+    $this->assertEquals(
+      array_keys($definition_before['process']),
+      array_keys($definition_after['process'])
+    );
+    $this->assertEquals(
+      $original_settings_process + ['formatter_fallback_test' => ['plugin' => 'get']],
+      $definition_after['process']['options/settings']
+    );
+  }
+
+  /**
+   * Tests that field widget alter of field migrate plugins are invoked.
+   *
+   * @see \Drupal\field_plugin_migrate_fallback_test\D7TextField::alterFieldWidgetMigration()
+   */
+  public function testWidgetFallback(): void {
+    $widget_migration = $this->getMigration('d6_field_instance_widget_settings');
+    $definition_before = $widget_migration->getPluginDefinition();
+    $original_settings_process = static::normalizeProcessPipeline($definition_before['process']['options/settings']);
+
+    $widget_migration->getProcess();
+
+    $definition_after = $widget_migration->getPluginDefinition();
+    $this->assertEquals(
+      array_keys($definition_before['process']),
+      array_keys($definition_after['process'])
+    );
+    $this->assertEquals(
+      $original_settings_process + ['widget_fallback_test' => ['plugin' => 'get']],
+      $definition_after['process']['options/settings']
+    );
+  }
+
+  /**
+   * Normalizes the given process pipeline.
+   *
+   * Ensures that the given process pipeline is a list of process pipeline
+   * configurations.
+   *
+   * @param string|array $process_pipeline
+   *   The process pipeline to normalize.
+   *
+   * @return array[]
+   *   The normalized process pipeline.
+   */
+  protected static function normalizeProcessPipeline($process_pipeline) {
+    if (is_string($process_pipeline)) {
+      return [['plugin' => 'get', 'source' => $process_pipeline]];
+    }
+    if (isset($process_pipeline['plugin'])) {
+      return [$process_pipeline];
+    }
+    return $process_pipeline;
+  }
+
+}
diff --git a/core/modules/field/tests/src/Kernel/Migrate/d7/MigrateFormatterWidgetFallbackTest.php b/core/modules/field/tests/src/Kernel/Migrate/d7/MigrateFormatterWidgetFallbackTest.php
new file mode 100644
index 0000000000..a6f2931bc7
--- /dev/null
+++ b/core/modules/field/tests/src/Kernel/Migrate/d7/MigrateFormatterWidgetFallbackTest.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace Drupal\Tests\field\Kernel\Migrate\d7;
+
+use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
+
+/**
+ * Tests formatter and widget fallback functionality.
+ */
+class MigrateFormatterWidgetFallbackTest extends MigrateDrupal7TestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'field_plugin_migrate_fallback_test',
+    'node',
+    'text',
+  ];
+
+  /**
+   * Tests that field formatter alter of field migrate plugins are invoked.
+   *
+   * @see \Drupal\field_plugin_migrate_fallback_test\D7TextField::alterFieldFormatterMigration()
+   */
+  public function testFormatterFallback(): void {
+    $formatter_migration = $this->getMigration('d7_field_formatter_settings');
+    $definition_before = $formatter_migration->getPluginDefinition();
+    $original_settings_process = static::normalizeProcessPipeline($definition_before['process']['options/settings']);
+
+    $formatter_migration->getProcess();
+
+    $definition_after = $formatter_migration->getPluginDefinition();
+    $this->assertEquals(
+      array_keys($definition_before['process']),
+      array_keys($definition_after['process'])
+    );
+    $this->assertEquals(
+      $original_settings_process + ['formatter_fallback_test' => ['plugin' => 'get']],
+      $definition_after['process']['options/settings']
+    );
+  }
+
+  /**
+   * Tests that field widget alter of field migrate plugins are invoked.
+   *
+   * @see \Drupal\field_plugin_migrate_fallback_test\D7TextField::alterFieldWidgetMigration()
+   */
+  public function testWidgetFallback(): void {
+    $widget_migration = $this->getMigration('d7_field_instance_widget_settings');
+    $definition_before = $widget_migration->getPluginDefinition();
+    $original_settings_process = static::normalizeProcessPipeline($definition_before['process']['options/settings']);
+
+    $widget_migration->getProcess();
+
+    $definition_after = $widget_migration->getPluginDefinition();
+    $this->assertEquals(
+      array_keys($definition_before['process']),
+      array_keys($definition_after['process'])
+    );
+    $this->assertEquals(
+      $original_settings_process + ['widget_fallback_test' => ['plugin' => 'get']],
+      $definition_after['process']['options/settings']
+    );
+  }
+
+  /**
+   * Normalizes the given process pipeline.
+   *
+   * Ensures that the given process pipeline is a list of process pipeline
+   * configurations.
+   *
+   * @param string|array $process_pipeline
+   *   The process pipeline to normalize.
+   *
+   * @return array[]
+   *   The normalized process pipeline.
+   */
+  protected static function normalizeProcessPipeline($process_pipeline) {
+    if (is_string($process_pipeline)) {
+      return [['plugin' => 'get', 'source' => $process_pipeline]];
+    }
+    if (isset($process_pipeline['plugin'])) {
+      return [$process_pipeline];
+    }
+    return $process_pipeline;
+  }
+
+}
diff --git a/core/modules/field/tests/src/Unit/Plugin/migrate/process/FieldMigrationPluginIdMapperTest.php b/core/modules/field/tests/src/Unit/Plugin/migrate/process/FieldMigrationPluginIdMapperTest.php
new file mode 100644
index 0000000000..5698079567
--- /dev/null
+++ b/core/modules/field/tests/src/Unit/Plugin/migrate/process/FieldMigrationPluginIdMapperTest.php
@@ -0,0 +1,345 @@
+<?php
+
+namespace Drupal\Tests\field\Unit\Plugin\migrate\process;
+
+use Drupal\Component\Plugin\Exception\PluginNotFoundException;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\field\Plugin\migrate\process\FieldMigrationPluginIdMapper;
+use Drupal\migrate\MigrateExecutable;
+use Drupal\migrate\Row;
+use Drupal\migrate_drupal\Plugin\migrate\FieldMigration;
+use Drupal\migrate_drupal\Plugin\MigrateFieldInterface;
+use Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface;
+use Drupal\Tests\migrate\Unit\MigrateTestCase;
+use Prophecy\Argument;
+
+/**
+ * Tests the FieldMigrationPluginIdMapper migrate process plugin.
+ *
+ * @coversDefaultClass \Drupal\field\Plugin\migrate\process\FieldMigrationPluginIdMapper
+ * @group field
+ */
+class FieldMigrationPluginIdMapperTest extends MigrateTestCase {
+
+  /**
+   * A \Drupal\migrate\Row prophecy.
+   *
+   * @var \Prophecy\Prophecy\ObjectProphecy
+   */
+  protected $row;
+
+  /**
+   * \Drupal\migrate_drupal\Plugin\MigrateFieldPluginManagerInterface prophecy.
+   *
+   * @var \Prophecy\Prophecy\ObjectProphecy
+   */
+  protected $migrateFieldPluginManager;
+
+  /**
+   * A \Drupal\migrate_drupal\Plugin\MigrateFieldInterface prophecy.
+   *
+   * @var \Prophecy\Prophecy\ObjectProphecy
+   */
+  protected $migrateFieldPlugin;
+
+  /**
+   * A \Drupal\migrate\MigrateExecutable prophecy.
+   *
+   * @var \Prophecy\Prophecy\ObjectProphecy
+   */
+  protected $migrateExecutable;
+
+  /**
+   * A \Drupal\migrate\MigrateExecutable prophecy.
+   *
+   * @var \Prophecy\Prophecy\ObjectProphecy
+   */
+  protected $moduleHandler;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $migrationConfiguration = [
+    'id' => 'test_field_config_migration',
+    'class' => FieldMigration::class,
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    $this->row = $this->prophesize(Row::class);
+    $this->migrateFieldPluginManager = $this->prophesize(MigrateFieldPluginManagerInterface::class);
+    $this->migrateFieldPlugin = $this->prophesize(MigrateFieldInterface::class);
+    $this->migrateExecutable = $this->prophesize(MigrateExecutable::class);
+    $this->moduleHandler = $this->prophesize(ModuleHandlerInterface::class);
+
+    parent::setUp();
+  }
+
+  /**
+   * Tests the transform method.
+   *
+   * @covers ::transform
+   * @dataProvider providerTestTransform
+   */
+  public function testTransform($source_value, array $transform_plugin_config, $migrate_field_plugin_conf, $hook_return, $expected_value, $exception_type = NULL, $exception_message = NULL) {
+    if ($migrate_field_plugin_conf) {
+      $this->migrateFieldPlugin->getFieldFormatterType($this->row->reveal())->willReturn($migrate_field_plugin_conf['type_return']);
+      $this->migrateFieldPlugin->getFieldWidgetType($this->row->reveal())->willReturn($migrate_field_plugin_conf['type_return']);
+      $this->migrateFieldPlugin->getFieldFormatterMap()->willReturn($migrate_field_plugin_conf['map_return']);
+      $this->migrateFieldPlugin->getFieldWidgetMap()->willReturn($migrate_field_plugin_conf['map_return']);
+    }
+
+    $this->moduleHandler
+      ->invokeAll('field_migration_field_formatter_info', Argument::any())
+      ->willReturn($hook_return);
+    $this->moduleHandler
+      ->invokeAll('field_migration_field_widget_info', Argument::any())
+      ->willReturn($hook_return);
+
+    if (is_array($source_value) && !empty($source_value[0])) {
+      if ($migrate_field_plugin_conf) {
+        $this->migrateFieldPluginManager
+          ->getPluginIdFromFieldType($source_value[0], Argument::cetera())
+          ->willReturn('foo');
+      }
+      else {
+        // Plugin not found exception.
+        $this->migrateFieldPluginManager
+          ->getPluginIdFromFieldType($source_value[0], Argument::cetera())
+          ->will(function () use ($source_value) {
+            throw new PluginNotFoundException($source_value[0]);
+          });
+      }
+    }
+
+    $this->migrateFieldPluginManager->createInstance('foo', Argument::any())->willReturn($this->migrateFieldPlugin->reveal());
+
+    if ($exception_type) {
+      $this->expectException($exception_type);
+      if ($exception_message) {
+        $this->expectExceptionMessage($exception_message);
+      }
+    }
+
+    $plugin = new FieldMigrationPluginIdMapper(
+      $transform_plugin_config,
+      'field_migration_plugin_id_mapper',
+      [],
+      $this->getMigration(),
+      $this->migrateFieldPluginManager->reveal(),
+      $this->moduleHandler->reveal()
+    );
+
+    $actual_transformed_value = $plugin->transform(
+      $source_value,
+      $this->migrateExecutable->reveal(),
+      $this->row->reveal(),
+      'bar'
+    );
+    $this->assertEquals($expected_value, $actual_transformed_value);
+  }
+
+  /**
+   * Data provider for testTransform.
+   *
+   * @return array
+   *   The test cases.
+   */
+  public function providerTestTransform() {
+    return [
+      'No field plugin' => [
+        'Source value' => [
+          'foo_source_field_type',
+          'foo_source_field_formatter_id',
+        ],
+        'Plugin config' => ['field_plugin' => 'field_formatter'],
+        'Migrate field plugin' => NULL,
+        'Hook return' => NULL,
+        'Expected' => [
+          'foo_source_field_type',
+          'foo_source_field_formatter_id',
+        ],
+      ],
+      'No plugin config' => [
+        'Source value' => NULL,
+        'Plugin config' => [],
+        'Migrate field plugin' => NULL,
+        'Hook return' => NULL,
+        'Expected' => NULL,
+        'Exception type' => \LogicException::class,
+        'Exception message' => 'The field_migration_plugin_id_mapper migrate process plugin requires a field_plugin configuration with a value set either to field_formatter or field_widget',
+      ],
+      'Invalid plugin config' => [
+        'Source value' => NULL,
+        'Plugin config' => ['field_plugin' => 'field_type'],
+        'Migrate field plugin' => NULL,
+        'Hook return' => NULL,
+        'Expected' => NULL,
+        'Exception type' => \LogicException::class,
+        'Exception message' => 'The field_migration_plugin_id_mapper migrate process plugin requires a field_plugin configuration with a value set either to field_formatter or field_widget',
+      ],
+      'Invalid source: string' => [
+        'Source value' => 'foo',
+        'Plugin config' => ['field_plugin' => 'field_formatter'],
+        'Migrate field plugin' => [],
+        'Hook return' => NULL,
+        'Expected' => [
+          'foo_source_field_type',
+          'foo_source_field_formatter_id',
+        ],
+        'Exception type' => \InvalidArgumentException::class,
+        'Exception message' => 'The field_migration_plugin_id_mapper migrate process plugin must have at least two source values with string type: The first one should be the field type in source, the second should be the source widget or formatter plugin ID',
+      ],
+      'Invalid source: only one item' => [
+        'Source value' => ['foo'],
+        'Plugin config' => ['field_plugin' => 'field_widget'],
+        'Migrate field plugin' => [],
+        'Hook return' => NULL,
+        'Expected' => NULL,
+        'Exception type' => \InvalidArgumentException::class,
+        'Exception message' => 'The field_migration_plugin_id_mapper migrate process plugin must have at least two source values with string type: The first one should be the field type in source, the second should be the source widget or formatter plugin ID',
+      ],
+      // Formatter mapping.
+      'Formatter ID mapped' => [
+        'Source value' => [
+          'foo_source_field_type',
+          'foo_source_field_formatter_id',
+        ],
+        'Plugin config' => ['field_plugin' => 'field_formatter'],
+        'Migrate field plugin' => [
+          'type_return' => 'foo_source_field_formatter_id',
+          'map_return' => [
+            'foo_source_field_formatter_id' => 'foo_destination_field_formatter_id',
+          ],
+        ],
+        'Hook return' => [],
+        'Expected' => 'foo_destination_field_formatter_id',
+      ],
+      'Formatter ID not mapped' => [
+        'Source value' => [
+          'foo_source_field_type',
+          'unmapped_source_field_formatter_id',
+        ],
+        'Plugin config' => ['field_plugin' => 'field_formatter'],
+        'Migrate field plugin' => [
+          'type_return' => 'unmapped_preprocessed_field_formatter_id',
+          'map_return' => [
+            'another_source_field_formatter_id' => 'another_destination_field_formatter_id',
+          ],
+        ],
+        'Hook return' => [],
+        'Expected' => 'unmapped_preprocessed_field_formatter_id',
+      ],
+      'Formatter ID mapped, with hook' => [
+        'Source value' => [
+          'foo_source_field_type',
+          'foo_source_field_formatter_id',
+        ],
+        'Plugin config' => ['field_plugin' => 'field_formatter'],
+        'Migrate field plugin' => [
+          'type_return' => 'foo_source_field_formatter_id',
+          'map_return' => [
+            'foo_source_field_formatter_id' => 'foo_destination_field_formatter_id',
+          ],
+        ],
+        'Hook return' => [
+          'foo_source_field_type' => [
+            'foo_source_field_formatter_id' => 'bar_field_formatter_id',
+          ],
+        ],
+        'Expected' => 'bar_field_formatter_id',
+      ],
+      'Formatter ID not mapped, with hook' => [
+        'Source value' => [
+          'foo_source_field_type',
+          'unmapped_source_field_formatter_id',
+        ],
+        'Plugin config' => ['field_plugin' => 'field_formatter'],
+        'Migrate field plugin' => [
+          'type_return' => 'unmapped_preprocessed_field_formatter_id',
+          'map_return' => [
+            'another_source_field_formatter_id' => 'another_destination_field_formatter_id',
+          ],
+        ],
+        'Hook return' => [
+          'foo_source_field_type' => [
+            'unmapped_source_field_formatter_id' => 'bar_field_formatter_id',
+          ],
+        ],
+        'Expected' => 'bar_field_formatter_id',
+      ],
+      // Widget mapping.
+      'Widget ID mapped' => [
+        'Source value' => [
+          'foo_source_field_type',
+          'foo_source_field_widget_id',
+        ],
+        'Plugin config' => ['field_plugin' => 'field_widget'],
+        'Migrate field plugin' => [
+          'type_return' => 'foo_source_field_widget_id',
+          'map_return' => [
+            'foo_source_field_widget_id' => 'foo_destination_field_widget_id',
+          ],
+        ],
+        'Hook return' => [],
+        'Expected' => 'foo_destination_field_widget_id',
+      ],
+      'Widget ID not mapped' => [
+        'Source value' => [
+          'foo_source_field_type',
+          'unmapped_foo_source_field_widget_id',
+        ],
+        'Plugin config' => ['field_plugin' => 'field_widget'],
+        'Migrate field plugin' => [
+          'type_return' => 'unmapped_foo_source_field_widget_id',
+          'map_return' => [
+            'foo_source_field_widget_id' => 'foo_destination_field_widget_id',
+          ],
+        ],
+        'Hook return' => [],
+        'Expected' => 'unmapped_foo_source_field_widget_id',
+      ],
+      'Widget ID mapped, with hook' => [
+        'Source value' => [
+          'foo_source_field_type',
+          'foo_source_field_widget_id',
+        ],
+        'Plugin config' => ['field_plugin' => 'field_widget'],
+        'Migrate field plugin' => [
+          'type_return' => 'foo_source_field_widget_id',
+          'map_return' => [
+            'foo_source_field_widget_id' => 'foo_destination_field_widget_id',
+          ],
+        ],
+        'Hook return' => [
+          'foo_source_field_type' => [
+            'foo_source_field_widget_id' => 'bar_field_widget_id',
+          ],
+        ],
+        'Expected' => 'bar_field_widget_id',
+      ],
+      'Widget ID not mapped, with hook' => [
+        'Source value' => [
+          'foo_source_field_type',
+          'unmapped_foo_source_field_widget_id',
+        ],
+        'Plugin config' => ['field_plugin' => 'field_widget'],
+        'Migrate field plugin' => [
+          'type_return' => 'unmapped_foo_source_field_widget_id',
+          'map_return' => [
+            'foo_source_field_widget_id' => 'foo_destination_field_widget_id',
+          ],
+        ],
+        'Hook return' => [
+          'foo_source_field_type' => [
+            'unmapped_foo_source_field_widget_id' => 'bar_field_widget_id',
+          ],
+        ],
+        'Expected' => 'bar_field_widget_id',
+      ],
+    ];
+  }
+
+}
diff --git a/core/modules/file/src/Plugin/migrate/process/d6/FieldFile.php b/core/modules/file/src/Plugin/migrate/process/d6/FieldFile.php
index f06eb2182e..c8a09c2d20 100644
--- a/core/modules/file/src/Plugin/migrate/process/d6/FieldFile.php
+++ b/core/modules/file/src/Plugin/migrate/process/d6/FieldFile.php
@@ -67,6 +67,10 @@ public static function create(ContainerInterface $container, array $configuratio
    * {@inheritdoc}
    */
   public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
+    // If '$value' is empty, then there is no file ID to look for.
+    if (empty($value)) {
+      return NULL;
+    }
     $options = unserialize($value['data']);
 
     // Try to look up the ID of the migrated file. If one cannot be found, it
diff --git a/core/modules/migrate_drupal/src/FieldDiscovery.php b/core/modules/migrate_drupal/src/FieldDiscovery.php
index f1c4745ece..3ffaec52f2 100644
--- a/core/modules/migrate_drupal/src/FieldDiscovery.php
+++ b/core/modules/migrate_drupal/src/FieldDiscovery.php
@@ -142,9 +142,8 @@ public function addBundleFieldProcesses(MigrationInterface $migration, $entity_t
     $bundle_fields = $fields[$entity_type_id][$bundle];
     foreach ($bundle_fields as $field_name => $field_info) {
       $plugin = $this->getFieldPlugin($field_info['type'], $migration);
+      $method = $plugin_definition['field_plugin_method'] ?? 'defineValueProcessPipeline';
       if ($plugin) {
-        $method = $plugin_definition['field_plugin_method'] ?? 'defineValueProcessPipeline';
-
         call_user_func_array([
           $plugin,
           $method,
@@ -154,11 +153,9 @@ public function addBundleFieldProcesses(MigrationInterface $migration, $entity_t
           $field_info,
         ]);
       }
-      else {
+      else if ($method === 'defineValueProcessPipeline') {
         // Default to a get process plugin if this is a value migration.
-        if ((empty($plugin_definition['field_plugin_method']) || $plugin_definition['field_plugin_method'] === 'defineValueProcessPipeline')) {
-          $migration->setProcessOfProperty($field_name, $field_name);
-        }
+        $migration->setProcessOfProperty($field_name, $field_name);
       }
     }
   }
diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/field/FieldPluginBase.php b/core/modules/migrate_drupal/src/Plugin/migrate/field/FieldPluginBase.php
index 201645d442..901f0393dd 100644
--- a/core/modules/migrate_drupal/src/Plugin/migrate/field/FieldPluginBase.php
+++ b/core/modules/migrate_drupal/src/Plugin/migrate/field/FieldPluginBase.php
@@ -38,6 +38,16 @@ public function alterFieldInstanceMigration(MigrationInterface $migration) {
    * {@inheritdoc}
    */
   public function alterFieldWidgetMigration(MigrationInterface $migration) {
+    $migration_processes = $migration->getProcess();
+    $predefined_type_map = $migration_processes['options/type']['type']['map'] ?? NULL;
+    // If the process with key 'type' is not a 'static_map' plugin, don't do
+    // anything.
+    if (
+      !is_array($predefined_type_map) ||
+      $migration_processes['options/type']['type']['plugin'] !== 'static_map'
+    ) {
+      return;
+    }
     $process = [];
     foreach ($this->getFieldWidgetMap() as $source_widget => $destination_widget) {
       $process['type']['map'][$source_widget] = $destination_widget;
@@ -84,6 +94,17 @@ public function getFieldWidgetMap() {
    * {@inheritdoc}
    */
   public function alterFieldFormatterMigration(MigrationInterface $migration) {
+    // If the process with key '0' is not a 'static_map' plugin, don't do
+    // anything.
+    $migration_processes = $migration->getProcess();
+    $predefined_type_map = $migration_processes['options/type'][0]['map'] ?? NULL;
+    if (
+      !is_array($predefined_type_map) ||
+      $migration_processes['options/type'][0]['plugin'] !== 'static_map'
+    ) {
+      return;
+    }
+
     $process = [];
     // Certain migrate field plugins do not have a type map annotation. For
     // these, the plugin ID is used for determining the source field type, which
