diff --git a/core/modules/ckeditor5/src/Plugin/Editor/CKEditor5.php b/core/modules/ckeditor5/src/Plugin/Editor/CKEditor5.php
index 87d46bcc5e32b4ae039cde51f5c1616af1dcd50a..9cdd6f9b46efbce5855cd0922afa8b3a06bf4ecf 100644
--- a/core/modules/ckeditor5/src/Plugin/Editor/CKEditor5.php
+++ b/core/modules/ckeditor5/src/Plugin/Editor/CKEditor5.php
@@ -20,9 +20,11 @@
 use Drupal\Core\Messenger\MessengerInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\ckeditor5\SmartDefaultSettings;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\Core\Validation\Plugin\Validation\Constraint\PrimitiveTypeConstraint;
+use Drupal\editor\Attribute\Editor;
 use Drupal\editor\EditorInterface;
-use Drupal\editor\Entity\Editor;
+use Drupal\editor\Entity\Editor as EditorEntity;
 use Drupal\editor\Plugin\EditorBase;
 use Drupal\filter\FilterFormatInterface;
 use Psr\Log\LoggerInterface;
@@ -33,20 +35,19 @@
 /**
  * Defines a CKEditor 5-based text editor for Drupal.
  *
- * @Editor(
- *   id = "ckeditor5",
- *   label = @Translation("CKEditor 5"),
- *   supports_content_filtering = TRUE,
- *   supports_inline_editing = TRUE,
- *   is_xss_safe = FALSE,
- *   supported_element_types = {
- *     "textarea"
- *   }
- * )
- *
  * @internal
  *   Plugin classes are internal.
  */
+#[Editor(
+  id: 'ckeditor5',
+  label: new TranslatableMarkup('CKEditor 5'),
+  supports_content_filtering: TRUE,
+  supports_inline_editing: TRUE,
+  is_xss_safe: FALSE,
+  supported_element_types: [
+    'textarea',
+  ]
+)]
 class CKEditor5 extends EditorBase implements ContainerFactoryPluginInterface {
 
   /**
@@ -532,7 +533,7 @@ public static function assessActiveTextEditorAfterBuild(array $element, FormStat
    */
   public static function validateSwitchingToCKEditor5(array $form, FormStateInterface $form_state): void {
     if (!$form_state->get('ckeditor5_is_active') && $form_state->get('ckeditor5_is_selected')) {
-      $minimal_ckeditor5_editor = Editor::create([
+      $minimal_ckeditor5_editor = EditorEntity::create([
         'format' => NULL,
         'editor' => 'ckeditor5',
       ]);
@@ -943,7 +944,7 @@ public function submitConfigurationForm(array &$form, FormStateInterface $form_s
   /**
    * {@inheritdoc}
    */
-  public function getJSSettings(Editor $editor) {
+  public function getJSSettings(EditorEntity $editor) {
     $toolbar_items = $editor->getSettings()['toolbar']['items'];
     $plugin_config = $this->ckeditor5PluginManager->getCKEditor5PluginConfig($editor);
 
@@ -965,7 +966,7 @@ public function getJSSettings(Editor $editor) {
   /**
    * {@inheritdoc}
    */
-  public function getLibraries(Editor $editor) {
+  public function getLibraries(EditorEntity $editor) {
     $plugin_libraries = $this->ckeditor5PluginManager->getEnabledLibraries($editor);
 
     if ($this->moduleHandler->moduleExists('locale')) {
diff --git a/core/modules/editor/src/Attribute/Editor.php b/core/modules/editor/src/Attribute/Editor.php
new file mode 100644
index 0000000000000000000000000000000000000000..d9aedf5631ab00de96bc0ab2ab92139a84d18a3a
--- /dev/null
+++ b/core/modules/editor/src/Attribute/Editor.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Drupal\editor\Attribute;
+
+use Drupal\Component\Plugin\Attribute\Plugin;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+
+/**
+ * Defines an Editor attribute object.
+ *
+ * Plugin Namespace: Plugin\Editor
+ *
+ * For a working example, see \Drupal\ckeditor5\Plugin\Editor\CKEditor5
+ *
+ * @see \Drupal\editor\Plugin\EditorPluginInterface
+ * @see \Drupal\editor\Plugin\EditorBase
+ * @see \Drupal\editor\Plugin\EditorManager
+ * @see hook_editor_info_alter()
+ * @see plugin_api
+ */
+#[\Attribute(\Attribute::TARGET_CLASS)]
+class Editor extends Plugin {
+
+  /**
+   * Constructs an Editor object.
+   *
+   * @param string $id
+   *   The plugin ID.
+   * @param \Drupal\Core\StringTranslation\TranslatableMarkup $label
+   *   The human-readable name of the text editor, translated
+   * @param bool $supports_content_filtering
+   *   Whether the editor supports "allowed content only" filtering.
+   * @param bool $supports_inline_editing
+   *   Whether the editor supports the inline editing provided by the Edit
+   *   module.
+   * @param bool $is_xss_safe
+   *   Whether this text editor is not vulnerable to XSS attacks.
+   * @param string[] $supported_element_types
+   *   On which form element #types this text editor is capable of working.
+   * @param class-string|null $deriver
+   *   (optional) The deriver class.
+   */
+  public function __construct(
+    public readonly string $id,
+    public readonly TranslatableMarkup $label,
+    public readonly bool $supports_content_filtering,
+    public readonly bool $supports_inline_editing,
+    public readonly bool $is_xss_safe,
+    public readonly array $supported_element_types,
+    public readonly ?string $deriver = NULL,
+  ) {}
+
+}
diff --git a/core/modules/editor/src/Plugin/EditorManager.php b/core/modules/editor/src/Plugin/EditorManager.php
index 4585da7e721a54a84b3054edf97e3e53ee5f2b49..82388b0e83abce179d5ef3ba577f838a4726a1be 100644
--- a/core/modules/editor/src/Plugin/EditorManager.php
+++ b/core/modules/editor/src/Plugin/EditorManager.php
@@ -5,6 +5,7 @@
 use Drupal\Core\Plugin\DefaultPluginManager;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\editor\Attribute\Editor;
 
 /**
  * Configurable text editor manager.
@@ -28,7 +29,7 @@ class EditorManager extends DefaultPluginManager {
    *   The module handler to invoke the alter hook with.
    */
   public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
-    parent::__construct('Plugin/Editor', $namespaces, $module_handler, 'Drupal\editor\Plugin\EditorPluginInterface', 'Drupal\editor\Annotation\Editor');
+    parent::__construct('Plugin/Editor', $namespaces, $module_handler, 'Drupal\editor\Plugin\EditorPluginInterface', Editor::class, 'Drupal\editor\Annotation\Editor');
     $this->alterInfo('editor_info');
     $this->setCacheBackend($cache_backend, 'editor_plugins');
   }
diff --git a/core/modules/editor/tests/modules/editor_test/src/Plugin/Editor/TRexEditor.php b/core/modules/editor/tests/modules/editor_test/src/Plugin/Editor/TRexEditor.php
index 70af7d822df80cea568a18209a5caba033385c69..aa620ad8050f1684ff706856e8a93da2ac62fc83 100644
--- a/core/modules/editor/tests/modules/editor_test/src/Plugin/Editor/TRexEditor.php
+++ b/core/modules/editor/tests/modules/editor_test/src/Plugin/Editor/TRexEditor.php
@@ -3,23 +3,24 @@
 namespace Drupal\editor_test\Plugin\Editor;
 
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\editor\Entity\Editor;
+use Drupal\editor\Attribute\Editor;
+use Drupal\editor\Entity\Editor as EditorEntity;
 use Drupal\editor\Plugin\EditorBase;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 
 /**
  * Defines a Tyrannosaurus-Rex powered text editor for testing purposes.
- *
- * @Editor(
- *   id = "trex",
- *   label = @Translation("TRex Editor"),
- *   supports_content_filtering = TRUE,
- *   supports_inline_editing = TRUE,
- *   is_xss_safe = FALSE,
- *   supported_element_types = {
- *     "textarea",
- *   }
- * )
  */
+#[Editor(
+  id: 'trex',
+  label: new TranslatableMarkup('TRex Editor'),
+  supports_content_filtering: TRUE,
+  supports_inline_editing: TRUE,
+  is_xss_safe: FALSE,
+  supported_element_types: [
+    'textarea',
+  ]
+)]
 class TRexEditor extends EditorBase {
 
   /**
@@ -44,7 +45,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
   /**
    * {@inheritdoc}
    */
-  public function getJSSettings(Editor $editor) {
+  public function getJSSettings(EditorEntity $editor) {
     $js_settings = [];
     $settings = $editor->getSettings();
     if ($settings['stumpy_arms']) {
@@ -56,7 +57,7 @@ public function getJSSettings(Editor $editor) {
   /**
    * {@inheritdoc}
    */
-  public function getLibraries(Editor $editor) {
+  public function getLibraries(EditorEntity $editor) {
     return [
       'editor_test/trex',
     ];
diff --git a/core/modules/editor/tests/modules/editor_test/src/Plugin/Editor/UnicornEditor.php b/core/modules/editor/tests/modules/editor_test/src/Plugin/Editor/UnicornEditor.php
index fc7e4f4544ef3bcca506c364061b2b770e614d72..b924db08b66292a8a4e35aa24ddf6839746b55c6 100644
--- a/core/modules/editor/tests/modules/editor_test/src/Plugin/Editor/UnicornEditor.php
+++ b/core/modules/editor/tests/modules/editor_test/src/Plugin/Editor/UnicornEditor.php
@@ -3,24 +3,25 @@
 namespace Drupal\editor_test\Plugin\Editor;
 
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\editor\Entity\Editor;
+use Drupal\editor\Attribute\Editor;
+use Drupal\editor\Entity\Editor as EditorEntity;
 use Drupal\editor\Plugin\EditorBase;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 
 /**
  * Defines a Unicorn-powered text editor for Drupal (for testing purposes).
- *
- * @Editor(
- *   id = "unicorn",
- *   label = @Translation("Unicorn Editor"),
- *   supports_content_filtering = TRUE,
- *   supports_inline_editing = TRUE,
- *   is_xss_safe = FALSE,
- *   supported_element_types = {
- *     "textarea",
- *     "textfield",
- *   }
- * )
  */
+#[Editor(
+  id: 'unicorn',
+  label: new TranslatableMarkup('Unicorn Editor'),
+  supports_content_filtering: TRUE,
+  supports_inline_editing: TRUE,
+  is_xss_safe: FALSE,
+  supported_element_types: [
+    'textarea',
+    'textfield',
+  ]
+)]
 class UnicornEditor extends EditorBase {
 
   /**
@@ -61,7 +62,7 @@ public function validateImageUploadSettings(array $element, FormStateInterface $
   /**
    * {@inheritdoc}
    */
-  public function getJSSettings(Editor $editor) {
+  public function getJSSettings(EditorEntity $editor) {
     $js_settings = [];
     $settings = $editor->getSettings();
     if ($settings['ponies_too']) {
@@ -73,7 +74,7 @@ public function getJSSettings(Editor $editor) {
   /**
    * {@inheritdoc}
    */
-  public function getLibraries(Editor $editor) {
+  public function getLibraries(EditorEntity $editor) {
     return [
       'editor_test/unicorn',
     ];