diff --git a/core/modules/views/src/Attribute/ViewsCache.php b/core/modules/views/src/Attribute/ViewsCache.php
new file mode 100644
index 0000000000000000000000000000000000000000..286eb6ebeadcb6fd8440c3d93fa353e966d3b91e
--- /dev/null
+++ b/core/modules/views/src/Attribute/ViewsCache.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Drupal\views\Attribute;
+
+use Drupal\Component\Plugin\Attribute\Plugin;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+
+/**
+ * Defines a views cache plugins type attribute for plugin discovery.
+ *
+ * @see \Drupal\views\Plugin\views\cache\CachePluginBase
+ *
+ * @ingroup views_cache_plugins
+ */
+#[\Attribute(\Attribute::TARGET_CLASS)]
+class ViewsCache extends Plugin {
+
+  /**
+   * Constructs a ViewsCache attribute.
+   *
+   * @param string $id
+   *   The plugin ID.
+   * @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $title
+   *   The plugin title used in the views UI.
+   * @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $short_title
+   *   (optional) The short title used in the views UI.
+   * @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $help
+   *   (optional) A short help string; this is displayed in the views UI.
+   * @param string[]|null $display_types
+   *   (optional) The types of the display this plugin can be used with.
+   *   For example the Feed display defines the type 'feed', so only rss style
+   *   and row plugins can be used in the views UI.
+   * @param string[] $base
+   *   The base tables on which this cache plugin can be used.
+   *   If no base table is specified the plugin can be used with all tables.
+   * @param bool $no_ui
+   *   Whether the plugin should be not selectable in the UI.
+   *   If set to TRUE, you can still use it via the API in config files.
+   * @param class-string|null $deriver
+   *   (optional) The deriver class.
+   */
+  public function __construct(
+    public readonly string $id,
+    public readonly ?TranslatableMarkup $title = NULL,
+    public readonly ?TranslatableMarkup $short_title = NULL,
+    public readonly ?TranslatableMarkup $help = NULL,
+    public readonly ?array $display_types = NULL,
+    public readonly array $base = [],
+    public readonly ?bool $no_ui = NULL,
+    public readonly ?string $deriver = NULL
+  ) {}
+
+}
diff --git a/core/modules/views/src/Plugin/views/cache/None.php b/core/modules/views/src/Plugin/views/cache/None.php
index fc9b58b238bb71d2597ebc3b974de5b2e5dc120c..5f0533f4aa22b86739db4cb83f10631ab0e01fce 100644
--- a/core/modules/views/src/Plugin/views/cache/None.php
+++ b/core/modules/views/src/Plugin/views/cache/None.php
@@ -2,17 +2,19 @@
 
 namespace Drupal\views\Plugin\views\cache;
 
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\views\Attribute\ViewsCache;
+
 /**
  * Caching plugin that provides no caching at all.
  *
  * @ingroup views_cache_plugins
- *
- * @ViewsCache(
- *   id = "none",
- *   title = @Translation("None"),
- *   help = @Translation("No caching of Views data.")
- * )
  */
+#[ViewsCache(
+  id: 'none',
+  title: new TranslatableMarkup('None'),
+  help: new TranslatableMarkup('No caching of Views data.'),
+)]
 class None extends CachePluginBase {
 
   public function summaryTitle() {
diff --git a/core/modules/views/src/Plugin/views/cache/Tag.php b/core/modules/views/src/Plugin/views/cache/Tag.php
index cc542c2519403d8e495d6638a009ad2adaa642fa..64608acf5ac75db2654f759a21fb1220b811188f 100644
--- a/core/modules/views/src/Plugin/views/cache/Tag.php
+++ b/core/modules/views/src/Plugin/views/cache/Tag.php
@@ -3,18 +3,19 @@
 namespace Drupal\views\Plugin\views\cache;
 
 use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\views\Attribute\ViewsCache;
 
 /**
  * Simple caching of query results for Views displays.
  *
  * @ingroup views_cache_plugins
- *
- * @ViewsCache(
- *   id = "tag",
- *   title = @Translation("Tag based"),
- *   help = @Translation("Tag based caching of data. Caches will persist until any related cache tags are invalidated.")
- * )
  */
+#[ViewsCache(
+  id: 'tag',
+  title: new TranslatableMarkup('Tag based'),
+  help: new TranslatableMarkup('Tag based caching of data. Caches will persist until any related cache tags are invalidated.'),
+)]
 class Tag extends CachePluginBase {
 
   /**
diff --git a/core/modules/views/src/Plugin/views/cache/Time.php b/core/modules/views/src/Plugin/views/cache/Time.php
index 17ad64e456cf1a9e22f1f1719481525bf6f118a2..24ae27de3f9da92c23d4121b4b08d3430275f074 100644
--- a/core/modules/views/src/Plugin/views/cache/Time.php
+++ b/core/modules/views/src/Plugin/views/cache/Time.php
@@ -6,19 +6,20 @@
 use Drupal\Core\Datetime\DateFormatterInterface;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\views\Attribute\ViewsCache;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Simple caching of query results for Views displays.
  *
  * @ingroup views_cache_plugins
- *
- * @ViewsCache(
- *   id = "time",
- *   title = @Translation("Time-based"),
- *   help = @Translation("Simple time-based caching of data.")
- * )
  */
+#[ViewsCache(
+  id: 'time',
+  title: new TranslatableMarkup('Time-based'),
+  help: new TranslatableMarkup('Simple time-based caching of data.'),
+)]
 class Time extends CachePluginBase {
 
   /**