diff --git a/src/Hook/TokenDefaultHooks.php b/src/Hook/TokenDefaultHooks.php
new file mode 100644
index 0000000..01cccd9
--- /dev/null
+++ b/src/Hook/TokenDefaultHooks.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace Drupal\token_default\Hook;
+
+use Drupal\Core\Render\BubbleableMetadata;
+use Drupal\token_default\Form\SettingsForm;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+/**
+ * Hook implementations for token_default.
+ */
+class TokenDefaultHooks
+{
+    use StringTranslationTrait;
+    /**
+     * Implements hook_help().
+     */
+    #[Hook('help')]
+    public function help($route_name, \Drupal\Core\Routing\RouteMatchInterface $route_match)
+    {
+        switch ($route_name) {
+            case 'help.page.token_default':
+                $readme = __DIR__ . '/README.md';
+                $text = file_get_contents($readme);
+                $output = '';
+                // If the Markdown module is installed, use it to render the README.
+                if ($text && \Drupal::moduleHandler()->moduleExists('markdown') === TRUE) {
+                    $filter_manager = \Drupal::service('plugin.manager.filter');
+                    $settings = \Drupal::configFactory()->get('markdown.settings')->getRawData();
+                    $config = [
+                        'settings' => $settings,
+                    ];
+                    $filter = $filter_manager->createInstance('markdown', $config);
+                    $output = $filter->process($text, 'en');
+                } else if ($text) {
+                    $output = '<pre>' . $text . '</pre>';
+                }
+                // Add a link to the Drupal.org project.
+                $output .= '<p>';
+                $output .= $this->t('Visit the <a href=":project_link">project pages</a> on Drupal.org for more information.', [
+                    ':project_link' => 'https://www.drupal.org/project/token_default',
+                ]);
+                $output .= '</p>';
+                return $output;
+        }
+    }
+    /**
+     * Implements hook_tokens_alter().
+     */
+    #[Hook('tokens_alter')]
+    public function tokensAlter(array &$replacements, array $context, \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata)
+    {
+        if (!\Drupal::config('token_default.settings')->get('enabled')) {
+            return;
+        }
+        $replacements = \Drupal::service('token_default.manager')->replaceMissingTokensWithDefaults($replacements, $context);
+        // Allows using another token as replacement value but prevent endless loop.
+        $recursive_calls = $context['data']['recursive_calls'] ?? 0;
+        $recursive_limit = \Drupal::config('token_default.settings')->get('recursive_limit') ?? \Drupal\token_default\Form\SettingsForm::DEFAULT_RECURSIVE_LIMIT;
+        if ($recursive_calls >= $recursive_limit) {
+            \Drupal::logger('token_default')->warning($this->t('Only @count recursive replacements processed to prevent endless loop', [
+                '@count' => $recursive_limit,
+            ]));
+            return;
+        }
+        $token_service = \Drupal::token();
+        foreach ($replacements as &$replacement) {
+            $tokens = $token_service->scan($replacement);
+            if ($tokens) {
+                $context['data']['recursive_calls'] = ++$recursive_calls;
+                $replacement = $token_service->replace($replacement, $context['data'], $context['options'], $bubbleable_metadata);
+            }
+        }
+    }
+}
diff --git a/token_default.module b/token_default.module
index 1b323fd..488f03e 100755
--- a/token_default.module
+++ b/token_default.module
@@ -6,7 +6,8 @@
  * found.
  * This can be altered per content type.
  */
-
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\token_default\Hook\TokenDefaultHooks;
 use Drupal\Core\Render\BubbleableMetadata;
 use Drupal\token_default\Form\SettingsForm;
 use Drupal\Core\Routing\RouteMatchInterface;
@@ -14,59 +15,17 @@ use Drupal\Core\Routing\RouteMatchInterface;
 /**
  * Implements hook_help().
  */
+#[LegacyHook]
 function token_default_help($route_name, RouteMatchInterface $route_match) {
-  switch ($route_name) {
-    case 'help.page.token_default':
-      $readme = __DIR__ . '/README.md';
-      $text = file_get_contents($readme);
-      $output = '';
-
-      // If the Markdown module is installed, use it to render the README.
-      if ($text && \Drupal::moduleHandler()->moduleExists('markdown') === TRUE) {
-        $filter_manager = \Drupal::service('plugin.manager.filter');
-        $settings = \Drupal::configFactory()->get('markdown.settings')->getRawData();
-        $config = ['settings' => $settings];
-        $filter = $filter_manager->createInstance('markdown', $config);
-        $output = $filter->process($text, 'en');
-      }
-      // Else the Markdown module is not installed output the README as text.
-      else if ($text) {
-        $output = '<pre>' . $text . '</pre>';
-      }
-
-      // Add a link to the Drupal.org project.
-      $output .= '<p>';
-      $output .= t('Visit the <a href=":project_link">project pages</a> on Drupal.org for more information.',[
-        ':project_link' => 'https://www.drupal.org/project/token_default'
-        ]);
-      $output .= '</p>';
-
-      return $output;
-  }
+  return \Drupal::service(TokenDefaultHooks::class)->help($route_name, $route_match);
 
 }
 
 /**
  * Implements hook_tokens_alter().
  */
-function token_default_tokens_alter(array &$replacements, array $context, BubbleableMetadata $bubbleable_metadata) {
-  if (!\Drupal::config('token_default.settings')->get('enabled')) {
-    return;
-  }
-  $replacements = \Drupal::service('token_default.manager')->replaceMissingTokensWithDefaults($replacements, $context);
-  // Allows using another token as replacement value but prevent endless loop.
-  $recursive_calls = $context['data']['recursive_calls'] ?? 0;
-  $recursive_limit = \Drupal::config('token_default.settings')->get('recursive_limit') ?? SettingsForm::DEFAULT_RECURSIVE_LIMIT;
-  if ($recursive_calls >= $recursive_limit) {
-    \Drupal::logger('token_default')->warning(t('Only @count recursive replacements processed to prevent endless loop', ['@count' => $recursive_limit]));
-    return;
-  }
-  $token_service = \Drupal::token();
-  foreach ($replacements as &$replacement) {
-    $tokens = $token_service->scan($replacement);
-    if ($tokens) {
-      $context['data']['recursive_calls'] = ++$recursive_calls;
-      $replacement = $token_service->replace($replacement, $context['data'], $context['options'], $bubbleable_metadata);
-    }
-  }
+#[LegacyHook]
+function token_default_tokens_alter(array &$replacements, array $context, BubbleableMetadata $bubbleable_metadata)
+{
+    \Drupal::service(TokenDefaultHooks::class)->tokensAlter($replacements, $context, $bubbleable_metadata);
 }
diff --git a/token_default.services.yml b/token_default.services.yml
index 4ab9568..7915709 100755
--- a/token_default.services.yml
+++ b/token_default.services.yml
@@ -2,3 +2,7 @@ services:
   token_default.manager:
     class: Drupal\token_default\TokenDefaultManager
     arguments: ['@entity_type.manager']
+
+  Drupal\token_default\Hook\TokenDefaultHooks:
+    class: Drupal\token_default\Hook\TokenDefaultHooks
+    autowire: true
