diff --git a/plugin.module b/plugin.module
index a5bf34d..1a626a0 100644
--- a/plugin.module
+++ b/plugin.module
@@ -5,6 +5,7 @@
  * Contains hook implementations.
  */
 
+use Drupal\field\FieldStorageConfigInterface;
 use Drupal\plugin\Plugin\Field\FieldType\PluginCollectionItemInterface;
 
 function plugin_field_info_alter(array &$field_type_definitions) {
@@ -33,6 +34,41 @@ function plugin_field_widget_info_alter(array &$field_widget_definitions) {
 }
 
 /**
+ * Implements hook_field_views_data_alter().
+ */
+function plugin_field_views_data_alter(array &$data, FieldStorageConfigInterface $field_storage) {
+  // Add the "plugin_id" filter to all configurable "plugin" fields.
+  if (strpos($field_storage->getType(), 'plugin:') === 0) {
+    $table_name = $field_storage->getTargetEntityTypeId() . '__' . $field_storage->getName();
+    $original_field_name = $field_storage->getName() . '_plugin_id';
+
+    // Skip if there is no Views data for this field.
+    if (!isset($data[$table_name][$original_field_name])) {
+      return;
+    }
+
+    $filter_field_name = $original_field_name . '_filter';
+    $data[$table_name][$filter_field_name] = [
+      'title' => t('@label (plugin ID filter)', [
+        '@label' => $field_storage->label(),
+      ]),
+      'group' => $data[$table_name][$original_field_name]['group'],
+      'help' => $data[$table_name][$original_field_name]['help'],
+      'filter' => [
+        'field' => $original_field_name,
+        'table' => $table_name,
+        'id' => 'plugin_id',
+        'additional fields' => [],
+        'field_name' => $field_storage->getName(),
+        'entity_type' => $field_storage->getTargetEntityTypeId(),
+        'plugin_type_id' => substr($field_storage->getType(), 7),
+        'allow empty' => TRUE,
+      ],
+    ];
+  }
+}
+
+/**
  * Gets the IDs of plugin item collection field types.
  *
  * @return string[]
diff --git a/src/Plugin/views/filter/PluginId.php b/src/Plugin/views/filter/PluginId.php
new file mode 100644
index 0000000..25cfefd
--- /dev/null
+++ b/src/Plugin/views/filter/PluginId.php
@@ -0,0 +1,121 @@
+<?php
+
+namespace Drupal\plugin\Plugin\views\filter;
+
+use Drupal\Core\Cache\CacheableMetadata;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\plugin\PluginDefinition\PluginLabelDefinitionInterface;
+use Drupal\plugin\PluginType\PluginTypeInterface;
+use Drupal\views\Plugin\views\filter\InOperator;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a Views filter for plugin IDs.
+ *
+ * @ingroup views_filter_handlers
+ *
+ * @ViewsFilter("plugin_id")
+ */
+final class PluginId extends InOperator implements ContainerFactoryPluginInterface {
+
+  /**
+   * The plugin type.
+   *
+   * @var \Drupal\plugin\PluginType\PluginTypeInterface
+   */
+  protected $pluginType;
+
+  /**
+   * Constructs a new instance.
+   *
+   * @param mixed[] $configuration
+   *   The plugin configuration.
+   * @param string $plugin_id
+   *   The plugin ID.
+   * @param mixed[] $plugin_definition
+   *   The plugin definition.
+   * @param \Drupal\plugin\PluginType\PluginTypeInterface $plugin_type
+   *   The plugin type.
+   */
+  public function __construct(array $configuration, $plugin_id, array $plugin_definition, PluginTypeInterface $plugin_type) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->pluginType = $plugin_type;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    /** @var \Drupal\plugin\PluginType\PluginTypeManagerInterface $plugin_type_manager */
+    $plugin_type_manager = $container->get('plugin.plugin_type_manager');
+
+    return new static($configuration, $plugin_id, $plugin_definition, $plugin_type_manager->getPluginType($configuration['plugin_type_id']));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getValueOptions() {
+    if (!is_null($this->valueOptions)) {
+      return $this->valueOptions;
+    }
+
+    $this->valueOptions = array_reduce($this->pluginType->getPluginManager()->getDefinitions(), function(array $value_options, $plugin_definition) {
+      $plugin_definition = $this->pluginType->ensureTypedPluginDefinition($plugin_definition);
+      $value_options[$plugin_definition->getId()] = $plugin_definition instanceof PluginLabelDefinitionInterface ? $plugin_definition->getLabel() : $plugin_definition->getId();
+      return $value_options;
+    }, []);
+    natcasesort($this->valueOptions);
+
+    return $this->valueOptions;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function valueForm(&$form, FormStateInterface $form_state) {
+    parent::valueForm($form, $form_state);
+    // Apply cacheability metadata, because the parent class does not.
+    $this->getCacheableMetadata()->applyTo($form);
+
+    return $form;
+  }
+
+  /**
+   * Gets this instance's cacheable metadata.
+   *
+   * @return \Drupal\Core\Cache\CacheableMetadata
+   */
+  protected function getCacheableMetadata() {
+    $cacheable_metadata = new CacheableMetadata();
+    $cacheable_metadata->addCacheableDependency($this->pluginType->getPluginManager());
+    $cacheable_metadata->addCacheTags(parent::getCacheTags());
+    $cacheable_metadata->addCacheContexts(parent::getCacheContexts());
+    $cacheable_metadata->mergeCacheMaxAge(parent::getCacheMaxAge());
+
+    return $cacheable_metadata;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheTags() {
+    return $this->getCacheableMetadata()->getCacheTags();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheContexts() {
+    return $this->getCacheableMetadata()->getCacheContexts();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheMaxAge() {
+    return $this->getCacheableMetadata()->getCacheMaxAge();
+  }
+
+}
diff --git a/tests/src/Unit/Plugin/views/filter/PluginIdTest.php b/tests/src/Unit/Plugin/views/filter/PluginIdTest.php
new file mode 100644
index 0000000..8fbdf93
--- /dev/null
+++ b/tests/src/Unit/Plugin/views/filter/PluginIdTest.php
@@ -0,0 +1,208 @@
+<?php
+
+namespace Drupal\Tests\plugin\Unit\Plugin\views\filter;
+
+use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheableDependencyInterface;
+use Drupal\plugin\Plugin\views\filter\PluginId;
+use Drupal\plugin\PluginDefinition\PluginDefinitionInterface;
+use Drupal\plugin\PluginDefinition\PluginLabelDefinitionInterface;
+use Drupal\plugin\PluginType\PluginTypeInterface;
+use Drupal\plugin\PluginType\PluginTypeManagerInterface;
+use Drupal\Tests\UnitTestCase;
+use Prophecy\Argument;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * @coversDefaultClass \Drupal\plugin\Plugin\views\filter\PluginId
+ *
+ * @group Plugin
+ */
+class PluginIdTest extends UnitTestCase {
+
+  /**
+   * The plugin type.
+   *
+   * @var \Drupal\plugin\PluginType\PluginTypeInterface|\Prophecy\Prophecy\ObjectProphecy
+   */
+  protected $pluginType;
+
+  /**
+   * The system under test.
+   *
+   * @var \Drupal\plugin\Plugin\views\filter\PluginId
+   */
+  protected $sut;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $plugin_type_id = 'foo_bar';
+    $plugin_id = '';
+    $plugin_definition = [
+      'id' => $plugin_id,
+    ];
+    $configuration = [
+      'plugin_type_id' => $plugin_type_id,
+    ];
+
+    $this->pluginType = $this->prophesize(PluginTypeInterface::class);
+
+    $this->sut = new PluginId($configuration, $plugin_id, $plugin_definition, $this->pluginType->reveal());
+  }
+
+  /**
+   * @covers ::create
+   * @covers ::__construct
+   */
+  public function testCreate() {
+    $plugin_type_id = 'foo_bar';
+    $plugin_id = '';
+    $plugin_definition = [
+      'id' => $plugin_id,
+    ];
+    $configuration = [
+      'plugin_type_id' => $plugin_type_id,
+    ];
+
+    $plugin_type_manager = $this->prophesize(PluginTypeManagerInterface::class);
+    $plugin_type_manager->getPluginType($plugin_type_id)->wilLReturn($this->pluginType->reveal());
+
+    $container = $this->prophesize(ContainerInterface::class);
+    $container->get('plugin.plugin_type_manager')->willReturn($plugin_type_manager->reveal());
+
+    $this->sut = PluginId::create($container->reveal(), $configuration, $plugin_id, $plugin_definition);
+    $this->assertInstanceOf(PluginId::class, $this->sut);
+  }
+
+  /**
+   * @covers ::getCacheContexts
+   * @covers ::getCacheableMetadata
+   */
+  public function testCacheContexts() {
+    $plugin_manager_cache_contexts = ['dog', 'ball'];
+
+    $plugin_manager = $this->prophesize(CacheableDependencyPluginManagerInterface::class);
+    $plugin_manager->getCacheContexts()->willReturn($plugin_manager_cache_contexts);
+    $plugin_manager->getCacheTags()->willReturn([]);
+    $plugin_manager->getCacheMaxAge()->willReturn(0);
+
+    $this->pluginType->getPluginManager()->willReturn($plugin_manager->reveal());
+
+    // Temporarily disable asserts, because Cache::mergeContexts() calls
+    // \Drupal::service(). See https://www.drupal.org/node/2720947.
+    assert_options(ASSERT_ACTIVE, FALSE);
+    $cache_contexts = $this->sut->getCacheContexts();
+    $this->assertInternalType('array', $cache_contexts);
+    foreach ($plugin_manager_cache_contexts as $plugin_manager_cache_context) {
+      $this->assertTrue(in_array($plugin_manager_cache_context, $cache_contexts));
+    }
+    assert_options(ASSERT_ACTIVE, TRUE);
+  }
+
+  /**
+   * @covers ::getCacheTags
+   * @covers ::getCacheableMetadata
+   */
+  public function testCacheTags() {
+    $plugin_manager_cache_tags = ['bar', 'foo'];
+
+    $plugin_manager = $this->prophesize(CacheableDependencyPluginManagerInterface::class);
+    $plugin_manager->getCacheContexts()->willReturn([]);
+    $plugin_manager->getCacheTags()->willReturn($plugin_manager_cache_tags);
+    $plugin_manager->getCacheMaxAge()->willReturn(0);
+
+    $this->pluginType->getPluginManager()->willReturn($plugin_manager->reveal());
+
+    $this->assertArraySubset($plugin_manager_cache_tags, $this->sut->getCacheTags());
+  }
+
+  /**
+   * @covers ::getCacheMaxAge
+   * @covers ::getCacheableMetadata
+   *
+   * @dataProvider provideCacheMaxAge
+   */
+  public function testCacheMaxAge($expected, $plugin_manager_max_age) {
+    $plugin_manager = $this->prophesize(CacheableDependencyPluginManagerInterface::class);
+    $plugin_manager->getCacheContexts()->willReturn([]);
+    $plugin_manager->getCacheTags()->willReturn([]);
+    $plugin_manager->getCacheMaxAge()->willReturn($plugin_manager_max_age);
+
+    $this->pluginType->getPluginManager()->willReturn($plugin_manager->reveal());
+
+    $this->assertSame($expected, $this->sut->getCacheMaxAge());
+  }
+
+  /**
+   * Provides data to self::testCacheMaxAge().
+   */
+  public function provideCacheMaxAge() {
+    $data = [];
+
+    $data['plugin-manager-permanent'] = [Cache::PERMANENT, Cache::PERMANENT];
+    $data['plugin-manager-never'] = [0, 0];
+    $data['plugin-manager-limited'] = [7, 7];
+
+    return $data;
+  }
+
+  /**
+   * @covers ::getValueOptions
+   * @covers ::getCacheableMetadata
+   */
+  public function testGetValueOptions() {
+    $plugin_label_1 = 'Foo';
+    $plugin_id_1 = 'aaa_foo';
+    $plugin_id_2 = 'baz';
+    $plugin_id_3 = 'qux';
+    $plugin_label_4 = 'Bar';
+    $plugin_id_4 = 'zzz_bar';
+
+    // Values must be sorted naturally.
+    $expected = [
+      $plugin_id_4 => $plugin_label_4,
+      $plugin_id_2 => $plugin_id_2,
+      $plugin_id_1 => $plugin_label_1,
+      $plugin_id_3 => $plugin_id_3,
+    ];
+
+    $plugin_definition_1 = $this->prophesize(PluginLabelDefinitionInterface::class);
+    $plugin_definition_1->getId()->willReturn($plugin_id_1);
+    $plugin_definition_1->getLabel()->willReturn($plugin_label_1);
+    $plugin_definition_2 = $this->prophesize(PluginDefinitionInterface::class);
+    $plugin_definition_2->getId()->willReturn($plugin_id_2);
+    $plugin_definition_3 = $this->prophesize(PluginDefinitionInterface::class);
+    $plugin_definition_3->getId()->willReturn($plugin_id_3);
+    $plugin_definition_4 = $this->prophesize(PluginLabelDefinitionInterface::class);
+    $plugin_definition_4->getId()->willReturn($plugin_id_4);
+    $plugin_definition_4->getLabel()->willReturn($plugin_label_4);
+
+    $plugin_definitions = [
+      $plugin_id_1 => $plugin_definition_1,
+      $plugin_id_2 => $plugin_definition_2,
+      $plugin_id_3 => $plugin_definition_3,
+      $plugin_id_4 => $plugin_definition_4,
+    ];
+
+    $plugin_manager = $this->prophesize(PluginManagerInterface::class);
+    $plugin_manager->getDefinitions()->willReturn($plugin_definitions);
+
+    $this->pluginType->ensureTypedPluginDefinition(Argument::any())->willReturnArgument();
+    $this->pluginType->getPluginManager()->willReturn($plugin_manager);
+
+    $this->assertSame($expected, $this->sut->getValueOptions());
+  }
+
+
+}
+
+/**
+ * Defines a plugin manager which is also a cacheable dependency.
+ */
+interface CacheableDependencyPluginManagerInterface extends PluginManagerInterface, CacheableDependencyInterface {
+}
