diff --git a/core/modules/dblog/src/Plugin/rest/resource/DbLogResource.php b/core/modules/dblog/src/Plugin/rest/resource/DbLogResource.php
index b1a617b3e1698f690e4602b50b689c9772ce0508..c3174469e5cff7e97dfdbb9e1b99049226733b61 100644
--- a/core/modules/dblog/src/Plugin/rest/resource/DbLogResource.php
+++ b/core/modules/dblog/src/Plugin/rest/resource/DbLogResource.php
@@ -3,6 +3,8 @@
 namespace Drupal\dblog\Plugin\rest\resource;
 
 use Drupal\Core\Database\Database;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\rest\Attribute\RestResource;
 use Drupal\rest\Plugin\ResourceBase;
 use Drupal\rest\ResourceResponse;
 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
@@ -10,15 +12,14 @@
 
 /**
  * Provides a resource for database watchdog log entries.
- *
- * @RestResource(
- *   id = "dblog",
- *   label = @Translation("Watchdog database log"),
- *   uri_paths = {
- *     "canonical" = "/dblog/{id}"
- *   }
- * )
  */
+#[RestResource(
+  id: "dblog",
+  label: new TranslatableMarkup("Watchdog database log"),
+  uri_paths: [
+    "canonical" => "/dblog/{id}",
+  ]
+)]
 class DbLogResource extends ResourceBase {
 
   /**
diff --git a/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php b/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php
index 5b54ca2a725c0ba638798b6404923456f0a3d1e3..1206acc77a29adc82544c26c154e2f880acc2d1f 100644
--- a/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php
+++ b/core/modules/file/src/Plugin/rest/resource/FileUploadResource.php
@@ -12,12 +12,14 @@
 use Drupal\Core\File\FileSystemInterface;
 use Drupal\Core\Lock\LockBackendInterface;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\Core\Utility\Token;
 use Drupal\file\Entity\File;
 use Drupal\file\Upload\ContentDispositionFilenameParser;
 use Drupal\file\Upload\InputStreamFileWriterInterface;
 use Drupal\file\Validation\FileValidatorInterface;
 use Drupal\file\Validation\FileValidatorSettingsTrait;
+use Drupal\rest\Attribute\RestResource;
 use Drupal\rest\ModifiedResourceResponse;
 use Drupal\rest\Plugin\ResourceBase;
 use Drupal\rest\Plugin\rest\resource\EntityResourceValidationTrait;
@@ -45,16 +47,15 @@
  *     to be later moved when they are referenced from a file field.
  *   - Permission to upload a file can be determined by a users field level
  *     create access to the file field.
- *
- * @RestResource(
- *   id = "file:upload",
- *   label = @Translation("File Upload"),
- *   serialization_class = "Drupal\file\Entity\File",
- *   uri_paths = {
- *     "create" = "/file/upload/{entity_type_id}/{bundle}/{field_name}"
- *   }
- * )
  */
+#[RestResource(
+  id: "file:upload",
+  label: new TranslatableMarkup("File Upload"),
+  serialization_class: File::class,
+  uri_paths: [
+    "create" => "/file/upload/{entity_type_id}/{bundle}/{field_name}",
+  ]
+)]
 class FileUploadResource extends ResourceBase {
 
   use FileValidatorSettingsTrait;
diff --git a/core/modules/rest/src/Attribute/RestResource.php b/core/modules/rest/src/Attribute/RestResource.php
new file mode 100644
index 0000000000000000000000000000000000000000..90722cc42808ba767ba3fe317aecba15d3c7a2e4
--- /dev/null
+++ b/core/modules/rest/src/Attribute/RestResource.php
@@ -0,0 +1,54 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\rest\Attribute;
+
+use Drupal\Component\Plugin\Attribute\Plugin;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+
+/**
+ * Defines a REST resource attribute object.
+ *
+ * Plugin Namespace: Plugin\rest\resource
+ *
+ * For a working example, see \Drupal\dblog\Plugin\rest\resource\DbLogResource
+ *
+ * @see \Drupal\rest\Plugin\Type\ResourcePluginManager
+ * @see \Drupal\rest\Plugin\ResourceBase
+ * @see \Drupal\rest\Plugin\ResourceInterface
+ * @see plugin_api
+ *
+ * @ingroup third_party
+ */
+#[\Attribute(\Attribute::TARGET_CLASS)]
+class RestResource extends Plugin {
+
+  /**
+   * Constructs a RestResource attribute.
+   *
+   * @param string $id
+   *   The REST resource plugin ID.
+   * @param \Drupal\Core\StringTranslation\TranslatableMarkup $label
+   *   The human-readable name of the REST resource plugin.
+   * @param string|null $serialization_class
+   *   (optional) The serialization class to deserialize serialized data into.
+   * @param string|null $deriver
+   *   (optional) The URI paths that this REST resource plugin provides.
+   *   - key: The link relation type plugin ID.
+   *   - value: The URL template.
+   * @param array $uri_paths
+   *   (optional) The deriver class for the rest resource.
+   *
+   * @see \Symfony\Component\Serializer\SerializerInterface
+   * @see core/core.link_relation_types.yml
+   */
+  public function __construct(
+    public readonly string $id,
+    public readonly TranslatableMarkup $label,
+    public readonly ?string $serialization_class = NULL,
+    public readonly ?string $deriver = NULL,
+    public readonly array $uri_paths = [],
+  ) {}
+
+}
diff --git a/core/modules/rest/src/Plugin/Type/ResourcePluginManager.php b/core/modules/rest/src/Plugin/Type/ResourcePluginManager.php
index 76fbca36e13f22defc5bba644cd9bc55ef9e93c4..3b41cdc734106e3e91522b1f5910dd352e32448d 100644
--- a/core/modules/rest/src/Plugin/Type/ResourcePluginManager.php
+++ b/core/modules/rest/src/Plugin/Type/ResourcePluginManager.php
@@ -5,6 +5,8 @@
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\rest\Attribute\RestResource;
+use Drupal\rest\Plugin\ResourceInterface;
 
 /**
  * Manages discovery and instantiation of resource plugins.
@@ -28,7 +30,14 @@ class ResourcePluginManager 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/rest/resource', $namespaces, $module_handler, 'Drupal\rest\Plugin\ResourceInterface', 'Drupal\rest\Annotation\RestResource');
+    parent::__construct(
+      'Plugin/rest/resource',
+      $namespaces,
+      $module_handler,
+      ResourceInterface::class,
+      RestResource::class,
+      'Drupal\rest\Annotation\RestResource',
+    );
 
     $this->setCacheBackend($cache_backend, 'rest_plugins');
     $this->alterInfo('rest_resource');
diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
index 45025ff4b9c171e2efd3d664580615077ac3e43f..7842194cfd8e738b967e405e5cc390f3e881283d 100644
--- a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
+++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
@@ -14,6 +14,9 @@
 use Drupal\Core\Entity\EntityStorageException;
 use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Routing\AccessAwareRouterInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\rest\Attribute\RestResource;
+use Drupal\rest\Plugin\Deriver\EntityDeriver;
 use Drupal\rest\Plugin\ResourceBase;
 use Drupal\rest\ResourceResponse;
 use Psr\Log\LoggerInterface;
@@ -29,18 +32,17 @@
  * Represents entities as resources.
  *
  * @see \Drupal\rest\Plugin\Deriver\EntityDeriver
- *
- * @RestResource(
- *   id = "entity",
- *   label = @Translation("Entity"),
- *   serialization_class = "Drupal\Core\Entity\Entity",
- *   deriver = "Drupal\rest\Plugin\Deriver\EntityDeriver",
- *   uri_paths = {
- *     "canonical" = "/entity/{entity_type}/{entity}",
- *     "create" = "/entity/{entity_type}"
- *   }
- * )
  */
+#[RestResource(
+  id: "entity",
+  label: new TranslatableMarkup("Entity"),
+  serialization_class: "Drupal\Core\Entity\Entity",
+  deriver: EntityDeriver::class,
+  uri_paths: [
+    "canonical" => "/entity/{entity_type}/{entity}",
+    "create" => "/entity/{entity_type}",
+  ],
+)]
 class EntityResource extends ResourceBase implements DependentPluginInterface {
 
   use EntityResourceValidationTrait;
diff --git a/core/modules/rest/tests/modules/rest_test/src/Plugin/rest/resource/NoSerializationClassTestResource.php b/core/modules/rest/tests/modules/rest_test/src/Plugin/rest/resource/NoSerializationClassTestResource.php
index 26895884818cd188c8cb821e6ebb2ba917a746d1..64e6f2c22ee0e9a47dd82605b547aca1e3144461 100644
--- a/core/modules/rest/tests/modules/rest_test/src/Plugin/rest/resource/NoSerializationClassTestResource.php
+++ b/core/modules/rest/tests/modules/rest_test/src/Plugin/rest/resource/NoSerializationClassTestResource.php
@@ -2,19 +2,20 @@
 
 namespace Drupal\rest_test\Plugin\rest\resource;
 
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\rest\Attribute\RestResource;
 use Drupal\rest\Plugin\ResourceBase;
 use Drupal\rest\ResourceResponse;
 
 /**
  * Class used to test that serialization_class is optional.
- *
- * @RestResource(
- *   id = "serialization_test",
- *   label = @Translation("Optional serialization_class"),
- *   serialization_class = "",
- *   uri_paths = {}
- * )
  */
+#[RestResource(
+  id: "serialization_test",
+  label: new TranslatableMarkup("Optional serialization_class"),
+  serialization_class: "",
+  uri_paths: []
+)]
 class NoSerializationClassTestResource extends ResourceBase {
 
   /**
diff --git a/core/modules/user/src/Plugin/rest/resource/UserRegistrationResource.php b/core/modules/user/src/Plugin/rest/resource/UserRegistrationResource.php
index 91167af9964f008073d54b68e7b7699d85a3f1cb..5b5d9204daafd0ac9a0d7455306a828b7395ad9f 100644
--- a/core/modules/user/src/Plugin/rest/resource/UserRegistrationResource.php
+++ b/core/modules/user/src/Plugin/rest/resource/UserRegistrationResource.php
@@ -4,10 +4,13 @@
 
 use Drupal\Core\Config\ImmutableConfig;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\rest\Attribute\RestResource;
 use Drupal\rest\ModifiedResourceResponse;
 use Drupal\rest\Plugin\ResourceBase;
 use Drupal\rest\Plugin\rest\resource\EntityResourceAccessTrait;
 use Drupal\rest\Plugin\rest\resource\EntityResourceValidationTrait;
+use Drupal\user\Entity\User;
 use Drupal\user\UserInterface;
 use Psr\Log\LoggerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -17,16 +20,15 @@
 
 /**
  * Represents user registration as a resource.
- *
- * @RestResource(
- *   id = "user_registration",
- *   label = @Translation("User registration"),
- *   serialization_class = "Drupal\user\Entity\User",
- *   uri_paths = {
- *     "create" = "/user/register",
- *   },
- * )
  */
+#[RestResource(
+  id: "user_registration",
+  label: new TranslatableMarkup("User registration"),
+  serialization_class: User::class,
+  uri_paths: [
+    "create" => "/user/register",
+  ],
+)]
 class UserRegistrationResource extends ResourceBase {
 
   use EntityResourceValidationTrait;