diff --git a/config_partial_export.info.yml b/config_partial_export.info.yml
index 9177e46..19f57cf 100644
--- a/config_partial_export.info.yml
+++ b/config_partial_export.info.yml
@@ -1,6 +1,6 @@
 name: 'Configuration Partial Export'
 type: module
-core_version_requirement: '^10 || ^11'
+core_version_requirement: ^10.1 || ^11 || ^12
 description: 'Partial export of selected configuration files.'
 package: Development
 dependencies:
diff --git a/config_partial_export.module b/config_partial_export.module
index a94f49e..9a25d4d 100644
--- a/config_partial_export.module
+++ b/config_partial_export.module
@@ -1,5 +1,12 @@
 <?php
 
+/**
+ * @file
+ */
+
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\config_partial_export\Hook\ConfigPartialExportHooks;
+
 /**
  * @file
  * Allows site administrators to modify configuration.
@@ -8,23 +15,7 @@
 /**
  * Implements hook_file_download().
  */
+#[LegacyHook]
 function config_partial_export_file_download($uri): array|bool {
-  $stream_wrapper_manager = \Drupal::service('stream_wrapper_manager');
-  $scheme = $stream_wrapper_manager->getScheme($uri);
-  $target = $stream_wrapper_manager->getTarget($uri);
-
-  if ($scheme !== 'temporary' || $target !== 'config_partial.tar.gz') {
-    return FALSE;
-  }
-
-  $request = \Drupal::request();
-  $date = DateTime::createFromFormat('U', $request->server->get('REQUEST_TIME'));
-  $date_string = $date->format('Y-m-d-H-i');
-  $hostname = str_replace('.', '-', $request->getHttpHost());
-  $filename = "config_partial-{$hostname}-{$date_string}.tar.gz";
-  $disposition = 'attachment; filename="' . $filename . '"';
-
-  return [
-    'Content-disposition' => $disposition,
-  ];
+  return \Drupal::service(ConfigPartialExportHooks::class)->fileDownload($uri);
 }
diff --git a/config_partial_export.services.yml b/config_partial_export.services.yml
new file mode 100644
index 0000000..c51b257
--- /dev/null
+++ b/config_partial_export.services.yml
@@ -0,0 +1,5 @@
+
+services:
+  Drupal\config_partial_export\Hook\ConfigPartialExportHooks:
+    class: Drupal\config_partial_export\Hook\ConfigPartialExportHooks
+    autowire: true
diff --git a/src/Hook/ConfigPartialExportHooks.php b/src/Hook/ConfigPartialExportHooks.php
new file mode 100644
index 0000000..84eae8e
--- /dev/null
+++ b/src/Hook/ConfigPartialExportHooks.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Drupal\config_partial_export\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for config_partial_export.
+ */
+class ConfigPartialExportHooks {
+  /**
+   * @file
+   * Allows site administrators to modify configuration.
+   */
+
+  /**
+   * Implements hook_file_download().
+   */
+  #[Hook('file_download')]
+  public static function fileDownload($uri): array|bool {
+    $stream_wrapper_manager = \Drupal::service('stream_wrapper_manager');
+    $scheme = $stream_wrapper_manager->getScheme($uri);
+    $target = $stream_wrapper_manager->getTarget($uri);
+    if ($scheme !== 'temporary' || $target !== 'config_partial.tar.gz') {
+      return FALSE;
+    }
+    $request = \Drupal::request();
+    $date = \DateTime::createFromFormat('U', $request->server->get('REQUEST_TIME'));
+    $date_string = $date->format('Y-m-d-H-i');
+    $hostname = str_replace('.', '-', $request->getHttpHost());
+    $filename = "config_partial-{$hostname}-{$date_string}.tar.gz";
+    $disposition = 'attachment; filename="' . $filename . '"';
+    return [
+      'Content-disposition' => $disposition,
+    ];
+  }
+
+}
diff --git a/tests/src/Unit/ConfigPartialExportCommandsTest.php b/tests/src/Unit/ConfigPartialExportCommandsTest.php
index 65999a0..9b39ac8 100644
--- a/tests/src/Unit/ConfigPartialExportCommandsTest.php
+++ b/tests/src/Unit/ConfigPartialExportCommandsTest.php
@@ -19,9 +19,9 @@ use Drush\Log\DrushLoggerManager;
  *  - writeConfig() — public, all I/O dependencies mocked.
  *
  * @group config_partial_export
- * @group legacy
  * @coversDefaultClass \Drupal\config_partial_export\Commands\ConfigPartialExportCommands
  */
+#[\PHPUnit\Framework\Attributes\IgnoreDeprecations]
 class ConfigPartialExportCommandsTest extends UnitTestCase {
 
   /**
@@ -76,7 +76,6 @@ class ConfigPartialExportCommandsTest extends UnitTestCase {
    */
   private function invokeGetMatchingConfigs(string $input, StorageInterface $storage): array {
     $ref = new \ReflectionMethod($this->commands, 'getMatchingConfigs');
-    $ref->setAccessible(TRUE);
     return $ref->invoke($this->commands, $input, $storage);
   }
 
