diff --git a/src/Hook/UrlReplaceFilterHooks.php b/src/Hook/UrlReplaceFilterHooks.php
new file mode 100644
index 0000000..1270fc1
--- /dev/null
+++ b/src/Hook/UrlReplaceFilterHooks.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Drupal\url_replace_filter\Hook;
+
+use Drupal\url_replace_filter\Plugin\Filter\UrlReplaceFilter;
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for url_replace_filter.
+ */
+class UrlReplaceFilterHooks {
+
+  /**
+   * Implements hook_help().
+   *
+   * @phpstan-return ?\Drupal\Core\StringTranslation\TranslatableMarkup
+   */
+  #[Hook('help')]
+  public static function help(string $route_name): mixed {
+    return UrlReplaceFilter::help($route_name);
+  }
+
+  /**
+   * Implements hook_theme().
+   *
+   * @phpstan-return array<string,mixed[]>
+   */
+  #[Hook('theme')]
+  public static function theme(): array {
+    $ret = [
+      'url_replace_filter_settings_form' => [
+        'render element' => UrlReplaceFilter::SETTING_NAME,
+      ],
+    ];
+    return $ret;
+  }
+
+}
diff --git a/tests/src/Unit/UrlReplaceFilterTest.php b/tests/src/Unit/UrlReplaceFilterTest.php
index 0630ef5..a2530a2 100644
--- a/tests/src/Unit/UrlReplaceFilterTest.php
+++ b/tests/src/Unit/UrlReplaceFilterTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\url_replace_filter\Unit;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\DataProvider;
 use Drupal\Core\Language\Language;
 use Drupal\Tests\UnitTestCase;
 use Drupal\url_replace_filter\Plugin\Filter\UrlReplaceFilter;
@@ -13,6 +15,7 @@ require_once DRUPAL_ROOT . '/core/includes/common.inc';
  * @coversDefaultClass \Drupal\url_replace_filter\Plugin\Filter\UrlReplaceFilter
  * @group filter
  */
+#[Group('filter')]
 class UrlReplaceFilterTest extends UnitTestCase {
 
   /**
@@ -32,6 +35,7 @@ class UrlReplaceFilterTest extends UnitTestCase {
    * @param string $expected
    *   The expected output string.
    */
+  #[DataProvider('providerProcess')]
   public function testProcess(string $html, string $expected): void {
     $this->assertSame($expected, $this->filter->process($html, Language::LANGCODE_SITE_DEFAULT)
       ->__toString());
diff --git a/url_replace_filter.info.yml b/url_replace_filter.info.yml
index 0461af6..5746c20 100755
--- a/url_replace_filter.info.yml
+++ b/url_replace_filter.info.yml
@@ -4,6 +4,6 @@ description: "Allows administrators to replace base URLs in &lt;img&gt; and &lt;
 dependencies:
   - drupal:filter
 package: Content display
-core_version_requirement: ^10 || ^11
+core_version_requirement: ^10.1 || ^11 || ^12
 configure: filter.admin_overview
 php: 8.2
diff --git a/url_replace_filter.install b/url_replace_filter.install
index 0f1279c..fcaa8bc 100644
--- a/url_replace_filter.install
+++ b/url_replace_filter.install
@@ -7,6 +7,8 @@
  * @license General Public License version 2.0 or later
  */
 
+use Drupal\Component\Utility\DeprecationHelper;
+use Drupal\Core\Extension\Requirement\RequirementSeverity;
 use Drupal\url_replace_filter\Plugin\Filter\UrlReplaceFilter;
 
 /**
@@ -32,11 +34,11 @@ function url_replace_filter_requirements(string $phase): array {
       'Used in 1 format.',
       'Used in @count formats.'
     );
-    $req['severity'] = REQUIREMENT_OK;
+    $req['severity'] = DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => RequirementSeverity::OK, fn() => REQUIREMENT_OK);
   }
   else {
     $req['value'] = t('Module is enabled but no format uses its filter. You should disable the module or use the filter in at least one format.');
-    $req['severity'] = REQUIREMENT_WARNING;
+    $req['severity'] = DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => RequirementSeverity::Warning, fn() => REQUIREMENT_WARNING);
   }
 
   return [UrlReplaceFilter::ID => $req];
diff --git a/url_replace_filter.module b/url_replace_filter.module
index 947fb3c..e16bae9 100755
--- a/url_replace_filter.module
+++ b/url_replace_filter.module
@@ -7,15 +7,17 @@
  * @license Licensed under the General Public License version 2.0 or later.
  */
 
-use Drupal\url_replace_filter\Plugin\Filter\UrlReplaceFilter;
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\url_replace_filter\Hook\UrlReplaceFilterHooks;
 
 /**
  * Implements hook_help().
  *
  * @phpstan-return ?\Drupal\Core\StringTranslation\TranslatableMarkup
  */
+#[LegacyHook]
 function url_replace_filter_help(string $route_name): mixed {
-  return UrlReplaceFilter::help($route_name);
+  return \Drupal::service(UrlReplaceFilterHooks::class)->help($route_name);
 }
 
 /**
@@ -23,12 +25,7 @@ function url_replace_filter_help(string $route_name): mixed {
  *
  * @phpstan-return array<string,mixed[]>
  */
+#[LegacyHook]
 function url_replace_filter_theme(): array {
-  $ret = [
-    'url_replace_filter_settings_form' => [
-      'render element' => UrlReplaceFilter::SETTING_NAME,
-    ],
-  ];
-
-  return $ret;
+  return \Drupal::service(UrlReplaceFilterHooks::class)->theme();
 }
diff --git a/url_replace_filter.services.yml b/url_replace_filter.services.yml
new file mode 100644
index 0000000..5b7bbab
--- /dev/null
+++ b/url_replace_filter.services.yml
@@ -0,0 +1,5 @@
+
+services:
+  Drupal\url_replace_filter\Hook\UrlReplaceFilterHooks:
+    class: Drupal\url_replace_filter\Hook\UrlReplaceFilterHooks
+    autowire: true
