diff --git a/core/modules/filter/migration_templates/d6_filter_format.yml b/core/modules/filter/migration_templates/d6_filter_format.yml
index 6b767f2..1a869e2 100644
--- a/core/modules/filter/migration_templates/d6_filter_format.yml
+++ b/core/modules/filter/migration_templates/d6_filter_format.yml
@@ -16,8 +16,9 @@ process:
     key: '@id'
     process:
       id:
-        plugin: static_map
-        default_value: filter_null
+        # Because the bypass flag is not set, the row will be skipped
+        # entirely if the filter ID cannot be determined.
+        plugin: filter_id
         source:
           - module
           - delta
diff --git a/core/modules/filter/migration_templates/d7_filter_format.yml b/core/modules/filter/migration_templates/d7_filter_format.yml
index 2b44a80..c0710b5 100644
--- a/core/modules/filter/migration_templates/d7_filter_format.yml
+++ b/core/modules/filter/migration_templates/d7_filter_format.yml
@@ -15,11 +15,16 @@ process:
     key: '@id'
     process:
       id:
-        plugin: static_map
+        # If the filter ID cannot be mapped, it will pass through unmodified
+        # because the bypass flag is set. When the user actually tries to
+        # view text through an invalid filter plugin, the filter system will
+        # fall back to filter_null and display a helpful error message.
+        plugin: filter_id
         bypass: true
         source: name
-        map:
-          php_code: filter_null
+        # No need to map anything -- filter plugin IDs haven't changed since
+        # Drupal 7.
+        map: { }
       settings:
         plugin: filter_settings
         source: settings
diff --git a/core/modules/filter/src/Plugin/migrate/process/FilterID.php b/core/modules/filter/src/Plugin/migrate/process/FilterID.php
new file mode 100644
index 0000000..578735f
--- /dev/null
+++ b/core/modules/filter/src/Plugin/migrate/process/FilterID.php
@@ -0,0 +1,71 @@
+<?php
+
+namespace Drupal\filter\Plugin\migrate\process;
+
+use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\migrate\MigrateExecutableInterface;
+use Drupal\migrate\Plugin\migrate\process\StaticMap;
+use Drupal\migrate\Row;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * @MigrateProcessPlugin(
+ *   id = "filter_id"
+ * )
+ */
+class FilterID extends StaticMap implements ContainerFactoryPluginInterface {
+
+  /**
+   * The filter plugin manager.
+   *
+   * @var \Drupal\Component\Plugin\PluginManagerInterface
+   */
+  protected $filterManager;
+
+  /**
+   * FilterID constructor.
+   *
+   * @param array $configuration
+   *   Plugin configuration.
+   * @param string $plugin_id
+   *   The plugin ID.
+   * @param mixed $plugin_definition
+   *   The plugin definition.
+   * @param \Drupal\Component\Plugin\PluginManagerInterface $filter_manager
+   *   The filter plugin manager.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, PluginManagerInterface $filter_manager) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->filterManager = $filter_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('plugin.manager.filter')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
+    $plugin_id = parent::transform($value, $migrate_executable, $row, $destination_property);
+
+    // Try to create an instance of the filter to ensure that it actually
+    // exists. If it doesn't, the filter manager will create an instance of
+    // its fallback plugin, filter_null, whose constructor will set a helpful
+    // error message like "Unknown filter [original filter ID]". We'll let the
+    // invalid value pass through anyway, since the filter system will always
+    // fall back to filter_null and the user can deal with it later.
+    $this->filterManager->createInstance($plugin_id);
+    return $plugin_id;
+  }
+
+}
diff --git a/core/modules/filter/tests/src/Kernel/Migrate/d6/MigrateFilterFormatTest.php b/core/modules/filter/tests/src/Kernel/Migrate/d6/MigrateFilterFormatTest.php
index ed5f0fb..3b2e8d7 100644
--- a/core/modules/filter/tests/src/Kernel/Migrate/d6/MigrateFilterFormatTest.php
+++ b/core/modules/filter/tests/src/Kernel/Migrate/d6/MigrateFilterFormatTest.php
@@ -3,6 +3,7 @@
 namespace Drupal\Tests\filter\Kernel\Migrate\d6;
 
 use Drupal\filter\Entity\FilterFormat;
+use Drupal\filter\FilterFormatInterface;
 use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
 
 /**
@@ -39,14 +40,16 @@ public function testFilterFormat() {
     $this->assertFalse(isset($filters['filter_html_image_secure']));
 
     // Check variables migrated into filter.
-    $this->assertIdentical('<a href hreflang> <em> <strong> <cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd>', $filters['filter_html']['settings']['allowed_html']);
-    $this->assertIdentical(TRUE, $filters['filter_html']['settings']['filter_html_help']);
-    $this->assertIdentical(FALSE, $filters['filter_html']['settings']['filter_html_nofollow']);
-    $this->assertIdentical(72, $filters['filter_url']['settings']['filter_url_length']);
+    $this->assertSame('<a href hreflang> <em> <strong> <cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd>', $filters['filter_html']['settings']['allowed_html']);
+    $this->assertSame(TRUE, $filters['filter_html']['settings']['filter_html_help']);
+    $this->assertSame(FALSE, $filters['filter_html']['settings']['filter_html_nofollow']);
+    $this->assertSame(72, $filters['filter_url']['settings']['filter_url_length']);
 
     // Check that the PHP code filter is converted to filter_null.
-    $filters = FilterFormat::load('php_code')->get('filters');
-    $this->assertTrue(isset($filters['filter_null']));
+    /** @var FilterFormatInterface $format */
+    $format = FilterFormat::load('php_code');
+    $this->assertInstanceOf(FilterFormatInterface::class, $format);
+    $this->assertArrayHasKey('filter_null', $format->get('filters'));
   }
 
 }
diff --git a/core/modules/filter/tests/src/Kernel/Migrate/d7/MigrateFilterFormatTest.php b/core/modules/filter/tests/src/Kernel/Migrate/d7/MigrateFilterFormatTest.php
index 8eb13b9..e4053ae 100644
--- a/core/modules/filter/tests/src/Kernel/Migrate/d7/MigrateFilterFormatTest.php
+++ b/core/modules/filter/tests/src/Kernel/Migrate/d7/MigrateFilterFormatTest.php
@@ -42,13 +42,13 @@ protected function setUp() {
   protected function assertEntity($id, $label, array $enabled_filters, $weight) {
     /** @var \Drupal\filter\FilterFormatInterface $entity */
     $entity = FilterFormat::load($id);
-    $this->assertTrue($entity instanceof FilterFormatInterface);
-    $this->assertIdentical($label, $entity->label());
+    $this->assertInstanceOf(FilterFormatInterface::class, $entity);
+    $this->assertSame($label, $entity->label());
     // get('filters') will return enabled filters only, not all of them.
-    $this->assertIdentical(array_keys($enabled_filters), array_keys($entity->get('filters')));
-    $this->assertIdentical($weight, $entity->get('weight'));
+    $this->assertSame(array_keys($enabled_filters), array_keys($entity->get('filters')));
+    $this->assertSame($weight, $entity->get('weight'));
     foreach ($entity->get('filters') as $filter_id => $filter) {
-      $this->assertIdentical($filter['weight'], $enabled_filters[$filter_id]);
+      $this->assertSame($filter['weight'], $enabled_filters[$filter_id]);
     }
   }
 
@@ -68,15 +68,17 @@ public function testFilterFormat() {
     // Ensure that filter-specific settings were migrated.
     /** @var \Drupal\filter\FilterFormatInterface $format */
     $format = FilterFormat::load('filtered_html');
+    $this->assertInstanceOf(FilterFormatInterface::class, $format);
     $config = $format->filters('filter_html')->getConfiguration();
-    $this->assertIdentical('<div> <span> <ul type> <li> <ol start type> <a href hreflang> <img src alt height width>', $config['settings']['allowed_html']);
+    $this->assertSame('<div> <span> <ul type> <li> <ol start type> <a href hreflang> <img src alt height width>', $config['settings']['allowed_html']);
     $config = $format->filters('filter_url')->getConfiguration();
-    $this->assertIdentical(128, $config['settings']['filter_url_length']);
+    $this->assertSame(128, $config['settings']['filter_url_length']);
 
     // The php_code format gets migrated, but the php_code filter is changed to
     // filter_null.
-    $filters = FilterFormat::load('php_code')->get('filters');
-    $this->assertTrue(isset($filters['filter_null']));
+    $format = FilterFormat::load('php_code');
+    $this->assertInstanceOf(FilterFormatInterface::class, $format);
+    $this->assertArrayHasKey('filter_null', $format->get('filters'));
   }
 
 }
