diff --git a/forward.install b/forward.install
index 22140a1..3247e19 100644
--- a/forward.install
+++ b/forward.install
@@ -192,8 +192,7 @@ function forward_update_8001() {
  */
 function forward_update_8200() {
   \Drupal::configFactory()->getEditable('forward.settings')
-    ->set('forward_form_allow_plain_text', FALSE)
-    ->save(TRUE);
+    ->set('forward_form_allow_plain_text', FALSE)->save();
 }
 
 /**
@@ -204,7 +203,7 @@ function forward_update_8201() {
   $format = $forward_settings->get('forward_filter_format');
   $forward_settings->set('forward_filter_format_html', $format);
   $forward_settings->set('forward_filter_format_plain_text', '');
-  $forward_settings->save(TRUE);
+  $forward_settings->save();
 }
 
 /**
@@ -212,8 +211,7 @@ function forward_update_8201() {
  */
 function forward_update_8202() {
   \Drupal::configFactory()->getEditable('forward.settings')
-    ->set('forward_email_footer', '')
-    ->save(TRUE);
+    ->set('forward_email_footer', '')->save();
 }
 
 /**
@@ -221,8 +219,7 @@ function forward_update_8202() {
  */
 function forward_update_8203() {
   \Drupal::configFactory()->getEditable('forward.settings')
-    ->clear('forward_max_recipients_error')
-    ->save(TRUE);
+    ->clear('forward_max_recipients_error')->save();
 }
 
 /**
diff --git a/forward.module b/forward.module
index 6851fc5..61c25b7 100644
--- a/forward.module
+++ b/forward.module
@@ -5,6 +5,10 @@
  * Allows forwarding of entities by email.
  */
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\forward\Hook\ForwardHooks;
+use Drupal\Component\Utility\DeprecationHelper;
+use Drupal\Core\Extension\ThemeSettingsProvider;
 use Drupal\Component\Utility\UrlHelper;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Url;
@@ -12,76 +16,33 @@ use Drupal\Core\Url;
 /**
  * Implements hook_help().
  */
+#[LegacyHook]
 function forward_help($route_name, RouteMatchInterface $route_match) {
-  switch ($route_name) {
-    case 'help.page.forward':
-      $output = '';
-      $output .= '<h3>' . t('About') . '</h3>';
-      $output .= '<p>' . t('Adds a "forward this page" link to pages.') . '</p>';
-      $output .= '<p>' . t('This module allows users to email a page on your site to a friend.') . '</p>';
-      return $output;
-
-    default:
-  }
+  return \Drupal::service(ForwardHooks::class)->help($route_name, $route_match);
 }
 
 /**
  * Implements hook_page_attachments().
  */
+#[LegacyHook]
 function forward_page_attachments(array &$page) {
-  if (\Drupal::routeMatch()->getRouteName() == 'forward.form') {
-    $settings = \Drupal::config('forward.settings')->get();
-
-    // Tell SEO to ignore this page on request.
-    if ($settings['forward_form_noindex']) {
-      $element = [
-        '#type' => 'html_tag',
-        '#tag' => 'meta',
-        '#attributes' => [
-          'name' => 'robots',
-          'content' => 'noindex, nofollow',
-        ],
-      ];
-      $page['#attached']['html_head'][] = [$element, 'forward_meta_noindex'];
-    }
-  }
+  \Drupal::service(ForwardHooks::class)->pageAttachments($page);
 }
 
 /**
  * Implements hook_theme().
  */
+#[LegacyHook]
 function forward_theme($existing, $type, $theme, $path) {
-  return [
-    'forward' => [
-      'variables' => [
-        'email' => NULL,
-        'recipient' => NULL,
-        'header' => NULL,
-        'footer' => NULL,
-        'message' => NULL,
-        'settings' => NULL,
-        'entity' => NULL,
-        'content' => NULL,
-        'view_mode' => NULL,
-        'email_format' => NULL,
-      ],
-    ],
-    'forward_link_formatter' => [
-      'variables' => [
-        'link' => NULL,
-      ],
-    ],
-  ];
+  return \Drupal::service(ForwardHooks::class)->theme($existing, $type, $theme, $path);
 }
 
 /**
  * Implements hook_theme_suggestions_HOOK().
  */
+#[LegacyHook]
 function forward_theme_suggestions_forward(array $variables) {
-  $suggestions = [];
-  $suggestions[] = 'forward__' . $variables['email_format'];
-  $suggestions[] = 'forward__' . $variables['email_format'] . '__' . $variables['view_mode'];
-  return $suggestions;
+  return \Drupal::service(ForwardHooks::class)->themeSuggestionsForward($variables);
 }
 
 /**
@@ -110,7 +71,7 @@ function template_preprocess_forward(&$variables) {
     $logo_url = Url::fromUri($uri, ['absolute' => TRUE])->toString();
   }
   else {
-    $logo_url = theme_get_setting('logo.url');
+    $logo_url = DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.3.0', fn() => \Drupal::service(ThemeSettingsProvider::class)->getSetting('logo.url'), fn() => theme_get_setting('logo.url'));
     if ($logo_url && !UrlHelper::isExternal($logo_url)) {
       $logo_url = Url::fromUri('base:/' . $logo_url, ['absolute' => TRUE])->toString();
     }
@@ -123,27 +84,15 @@ function template_preprocess_forward(&$variables) {
 /**
  * Implements hook_mail().
  */
+#[LegacyHook]
 function forward_mail($key, &$message, $params) {
-  $message['subject'] = $params['subject'];
-  $message['body'][] = $params['body'];
+  \Drupal::service(ForwardHooks::class)->mail($key, $message, $params);
 }
 
 /**
  * Implements hook_mail_alter().
  */
+#[LegacyHook]
 function forward_mail_alter(&$message) {
-  if ($message['module'] == 'forward') {
-    if (($message['key'] == 'mail_entity') || ($message['key'] == 'send_entity')) {
-      if (!empty($message['params']['plain_text'])) {
-        $message['headers']['Content-Type'] = 'text/plain; charset=utf-8';
-      }
-      else {
-        $message['headers']['Content-Type'] = 'text/html; charset=utf-8';
-      }
-
-      $message['headers']['MIME-Version'] = '1.0';
-      $message['headers']['Reply-to'] = $message['params']['headers']['Reply-To'];
-      $message['headers']['Sender'] = $message['headers']['From'];
-    }
-  }
+  \Drupal::service(ForwardHooks::class)->mailAlter($message);
 }
diff --git a/forward.services.yml b/forward.services.yml
index f5ff196..8c75c08 100644
--- a/forward.services.yml
+++ b/forward.services.yml
@@ -25,3 +25,15 @@ services:
       '@renderer',
       '@token',
       '@extension.list.module']
+
+  Drupal\forward\Hook\ForwardHooks:
+    class: Drupal\forward\Hook\ForwardHooks
+    autowire: true
+
+  Drupal\forward\Hook\ForwardTokensHooks:
+    class: Drupal\forward\Hook\ForwardTokensHooks
+    autowire: true
+
+  Drupal\forward\Hook\ForwardViewsHooks:
+    class: Drupal\forward\Hook\ForwardViewsHooks
+    autowire: true
diff --git a/forward.tokens.inc b/forward.tokens.inc
index c09aeb9..6dcfd16 100644
--- a/forward.tokens.inc
+++ b/forward.tokens.inc
@@ -5,131 +5,22 @@
  * Provide tokens related to the forward module.
  */
 
-use Drupal\Component\Utility\Xss;
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\forward\Hook\ForwardTokensHooks;
 use Drupal\Core\Render\BubbleableMetadata;
-use Drupal\Core\Render\Markup;
 
 /**
  * Implements hook_token_info().
  */
+#[LegacyHook]
 function forward_token_info() {
-  $data['types'] = [
-    'forward' => [
-      'name' => t('Forward'),
-      'description' => t('Tokens related to forward module.'),
-    ],
-  ];
-  $data['tokens'] = [
-    'forward' => [
-      'recipients' => [
-        'name' => t("Email recipient(s)"),
-        'description' => t("Recipient email address or email addresses for forwarded content."),
-      ],
-      'sender-name' => [
-        'name' => t("Sender's name"),
-        'description' => t("Name of sender as entered in Forwarding form."),
-      ],
-      'sender-email' => [
-        'name' => t("Sender's email address"),
-        'description' => t("Email address of sender as entered in Forwarding form."),
-      ],
-      'entity-type' => [
-        'name' => t("Entity type"),
-        'description' => t('The type of entity being forwarded. For nodes, uses the bundle name, e.g. "article" or "page", instead of the type.'),
-      ],
-      'entity-title' => [
-        'name' => t("Entity title"),
-        'description' => t('The title of the entity being forwarded.'),
-      ],
-      'entity-url' => [
-        'name' => t('Entity URL'),
-        'description' => t('The absolute URL for the entity being forwarded.'),
-      ],
-      'entity-link' => [
-        'name' => t('Entity link'),
-        'description' => t('A formatted link (using title and URL) of the entity being forwarded.'),
-      ],
-    ],
-  ];
-  return $data;
+  return \Drupal::service(ForwardTokensHooks::class)->tokenInfo();
 }
 
 /**
  * Implements hook_tokens().
  */
+#[LegacyHook]
 function forward_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
-  $replacements = [];
-  if (($type == 'forward') && !empty($data['forward'])) {
-    foreach ($tokens as $name => $original) {
-      switch ($name) {
-        case 'entity-type':
-          if (isset($data['forward']['entity'])) {
-            $entity = $data['forward']['entity'];
-            $entity_type = (string) $entity->getEntityType()->getLabel();
-            if ($entity->getEntityType()->hasKey('bundle')) {
-              // For entity types with bundles, use the bundle label
-              // instead of the entity type label.
-              $bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo($entity->getEntityTypeId());
-              $entity_type = $bundles[$entity->bundle()]['label'];
-            }
-            // Use the translated label, then lowercase it.
-            $entity_type = strtolower(t('@entity_type', ['@entity_type' => $entity_type], $options));
-            $replacements[$original] = Markup::create(Xss::filter($entity_type));
-          }
-          else {
-            $replacements[$original] = t('page');
-          }
-          break;
-
-        case 'entity-title':
-          if (isset($data['forward']['entity'])) {
-            $entity = $data['forward']['entity'];
-            $entity_title = $entity->access('view') ? $entity->label() : t('a page');
-            $replacements[$original] = Markup::create(Xss::filter($entity_title));
-          }
-          else {
-            $replacements[$original] = t('a page');
-          }
-          break;
-
-        case 'entity-url':
-          if (isset($data['forward']['entity'])) {
-            $entity = $data['forward']['entity'];
-            $entity_url = $entity->access('view') ? $entity->toUrl('canonical', ['absolute' => TRUE])->toString() : t('a URL');
-            $replacements[$original] = Markup::create(Xss::filter($entity_url));
-          }
-          else {
-            $replacements[$original] = t('a URL');
-          }
-          break;
-
-        case 'entity-link':
-          if (isset($data['forward']['entity'])) {
-            $entity = $data['forward']['entity'];
-            $entity_link = $entity->access('view') ? $entity->toLink($entity->label())->toString() : t('a link');
-            $replacements[$original] = Markup::create(Xss::filter($entity_link));
-          }
-          else {
-            $replacements[$original] = t('a link');
-          }
-          break;
-
-        case 'recipients':
-          $replacements[$original] = isset($data['forward']['recipients']) ?
-            Markup::create(Xss::filter(implode(', ', $data['forward']['recipients']))) : t('(Recipient list)');
-          break;
-
-        case 'sender-name':
-          $replacements[$original] = isset($data['forward']['sender_name']) ?
-            Markup::create(Xss::filter($data['forward']['sender_name'])) : t('(Your name)');
-          break;
-
-        case 'sender-email':
-          $replacements[$original] = isset($data['forward']['sender_email']) ?
-            Markup::create(Xss::filter($data['forward']['sender_email'])) : t('(Your email address)');
-          break;
-      }
-    }
-  }
-  return $replacements;
+  return \Drupal::service(ForwardTokensHooks::class)->tokens($type, $tokens, $data, $options, $bubbleable_metadata);
 }
diff --git a/forward.views.inc b/forward.views.inc
index 73d1791..302c40d 100644
--- a/forward.views.inc
+++ b/forward.views.inc
@@ -1,5 +1,12 @@
 <?php
 
+/**
+ * @file
+ */
+
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\forward\Hook\ForwardViewsHooks;
+
 /**
  * @file
  * Provide views data.
@@ -8,175 +15,15 @@
 /**
  * Implements hook_views_data().
  */
+#[LegacyHook]
 function forward_views_data() {
-  $data = [];
-
-  // Forward statistics.
-  // Define the base group of this table. Fields that don't
-  // have a group defined will go into this field by default.
-  $data['forward_statistics']['table'] = [
-    'group' => t('Forward statistics'),
-    'wizard_id' => 'forward_statistics',
-  ];
-
-  // For other entity base tables, explain how we join.
-  $entity_types = \Drupal::entityTypeManager()->getDefinitions();
-  foreach ($entity_types as $type => $info) {
-    if (is_a($info, 'Drupal\Core\Entity\ContentEntityType')) {
-      if ($info->getBaseTable()) {
-        $data['forward_statistics']['table']['join'][$info->getBaseTable() . '_field_data'] = [
-          // 'left_field' is the primary key in the referenced table.
-          // 'field' is the foreign key in this table.
-          'left_field' => $info->getKey('id'),
-          'field' => 'id',
-          'extra' => [
-            ['field' => 'type', 'value' => $type],
-          ],
-        ];
-      }
-    }
-  }
-
-  // Forward statistics fields.
-  $data['forward_statistics']['forward_count'] = [
-    'title' => t('Forward count'),
-    'help' => t('The number of times an entity was forwarded.'),
-    'field' => [
-      'id' => 'numeric',
-      'click sortable' => TRUE,
-    ],
-    'filter' => [
-      'id' => 'numeric',
-    ],
-    'sort' => [
-      'id' => 'standard',
-    ],
-  ];
-  $data['forward_statistics']['last_forward_timestamp'] = [
-    'title' => t('Most recent forward'),
-    'help' => t('The last time an entity was forwarded.'),
-    'field' => [
-      'id' => 'date',
-      'click sortable' => TRUE,
-    ],
-    'filter' => [
-      'id' => 'date',
-    ],
-    'sort' => [
-      'id' => 'standard',
-    ],
-  ];
-
-  // Forward log.
-  $data['forward_log']['table'] = [
-    'group' => t('Forward log'),
-    'wizard_id' => 'forward_log',
-  ];
-
-  // Forward log fields.
-  $data['forward_log']['path'] = [
-    'title' => t('Path'),
-    'help' => t('The path that was forwarded.'),
-    'field' => [
-      'id' => 'standard',
-      'click sortable' => TRUE,
-    ],
-    'argument' => [
-      'id' => 'string',
-    ],
-    'filter' => [
-      'id' => 'standard',
-    ],
-    'sort' => [
-      'id' => 'standard',
-    ],
-  ];
-  $data['forward_log']['timestamp'] = [
-    'title' => t('Timestamp'),
-    'help' => t('The date and time of the forward.'),
-    'field' => [
-      'id' => 'date',
-      'click sortable' => TRUE,
-    ],
-    'filter' => [
-      'id' => 'date',
-    ],
-    'sort' => [
-      'id' => 'standard',
-    ],
-  ];
-  $data['forward_log']['uid'] = [
-    'title' => t('UID'),
-    'help' => t('The user ID of the user who forwarded.'),
-    'field' => [
-      'id' => 'numeric',
-    ],
-    'argument' => [
-      'id' => 'numeric',
-    ],
-    'filter' => [
-      'id' => 'numeric',
-    ],
-    'search' => [
-      'id' => 'standard',
-    ],
-    'relationship' => [
-      'title' => t('User'),
-      'label' => t('User referenced by Forward log'),
-      'help' => t('The user that forwarded.'),
-      'base' => 'users',
-      'base field' => 'uid',
-      'id' => 'standard',
-    ],
-  ];
-  $data['forward_log']['hostname'] = [
-    'title' => t('Hostname'),
-    'help' => t('Hostname of the user who forwarded.'),
-    'field' => [
-      'id' => 'standard',
-    ],
-    'argument' => [
-      'id' => 'string',
-    ],
-    'filter' => [
-      'id' => 'standard',
-    ],
-    'sort' => [
-      'id' => 'standard',
-    ],
-  ];
-
-  return $data;
+  return \Drupal::service(ForwardViewsHooks::class)->viewsData();
 }
 
 /**
  * Implements hook_views_data_alter().
  */
+#[LegacyHook]
 function forward_views_data_alter(&$data) {
-  $entity_types = \Drupal::entityTypeManager()->getDefinitions();
-  foreach ($entity_types as $type => $info) {
-    if (is_a($info, 'Drupal\Core\Entity\ContentEntityType')) {
-      if ($info->getBaseTable()) {
-        // Add a relationship to the logs.
-        $data[$info->getBaseTable() . '_field_data']['forward_log'] = [
-          'title' => t('Entity with forward logs'),
-          'help' => t('Relate all forward logs for the entity. Creates a row for each instance the entity was forwarded.'),
-          'relationship' => [
-            'group' => t('Forward log'),
-            'label' => t('Forward log referenced by entity'),
-            'base' => 'forward_log',
-            'base field' => 'id',
-            'relationship field' => $info->getKey('id'),
-            'id' => 'standard',
-            'extra' => [
-              [
-                'field' => 'type',
-                'value' => $type,
-              ],
-            ],
-          ],
-        ];
-      }
-    }
-  }
+  \Drupal::service(ForwardViewsHooks::class)->viewsDataAlter($data);
 }
diff --git a/src/Form/ForwardForm.php b/src/Form/ForwardForm.php
index d1c8a93..f22ca1a 100644
--- a/src/Form/ForwardForm.php
+++ b/src/Form/ForwardForm.php
@@ -543,7 +543,7 @@ class ForwardForm extends FormBase implements BaseFormIdInterface {
         // This filter was setup by the Forward administrator for this
         // purpose only, whose permission to run the filter was checked
         // at that time. Therefore, no need to check filter access again here.
-        $params['body'] = check_markup($params['body'], $this->settings[$format_setting], $langcode);
+        $params['body'] = ['#type' => 'processed_text', '#text' => $params['body'], '#format' => $this->settings[$format_setting], '#langcode' => $langcode];
       }
 
       // Allow modules to alter the final message body.
diff --git a/src/Form/SettingsForm.php b/src/Form/SettingsForm.php
index 4c5d192..2ff7baa 100644
--- a/src/Form/SettingsForm.php
+++ b/src/Form/SettingsForm.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\forward\Form;
 
+use Drupal\Component\Utility\DeprecationHelper;
+use Drupal\filter\FilterFormatRepositoryInterface;
 use Drupal\Core\Form\ConfigFormBase;
 use Drupal\Core\Form\FormStateInterface;
 
@@ -179,7 +181,7 @@ class SettingsForm extends ConfigFormBase {
     // Post processing filters.
     $filter_options = [];
     $filter_options[''] = $this->t('- None -');
-    foreach (filter_formats($this->currentUser()) as $key => $format) {
+    foreach (DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => \Drupal::service(FilterFormatRepositoryInterface::class)->getFormatsForAccount($this->currentUser()), fn() => filter_formats($this->currentUser())) as $key => $format) {
       $filter_options[$key] = $format->label();
     }
     if (count($filter_options) > 1) {
diff --git a/src/Hook/ForwardHooks.php b/src/Hook/ForwardHooks.php
new file mode 100644
index 0000000..acca550
--- /dev/null
+++ b/src/Hook/ForwardHooks.php
@@ -0,0 +1,125 @@
+<?php
+
+namespace Drupal\forward\Hook;
+
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for forward.
+ */
+class ForwardHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_help().
+   */
+  #[Hook('help')]
+  public function help($route_name, RouteMatchInterface $route_match) {
+    switch ($route_name) {
+      case 'help.page.forward':
+        $output = '';
+        $output .= '<h3>' . $this->t('About') . '</h3>';
+        $output .= '<p>' . $this->t('Adds a "forward this page" link to pages.') . '</p>';
+        $output .= '<p>' . $this->t('This module allows users to email a page on your site to a friend.') . '</p>';
+        return $output;
+
+      default:
+    }
+  }
+
+  /**
+   * Implements hook_page_attachments().
+   */
+  #[Hook('page_attachments')]
+  public function pageAttachments(array &$page) {
+    if (\Drupal::routeMatch()->getRouteName() == 'forward.form') {
+      $settings = \Drupal::config('forward.settings')->get();
+      // Tell SEO to ignore this page on request.
+      if ($settings['forward_form_noindex']) {
+        $element = [
+          '#type' => 'html_tag',
+          '#tag' => 'meta',
+          '#attributes' => [
+            'name' => 'robots',
+            'content' => 'noindex, nofollow',
+          ],
+        ];
+        $page['#attached']['html_head'][] = [
+          $element,
+          'forward_meta_noindex',
+        ];
+      }
+    }
+  }
+
+  /**
+   * Implements hook_theme().
+   */
+  #[Hook('theme')]
+  public function theme($existing, $type, $theme, $path) {
+    return [
+      'forward' => [
+        'variables' => [
+          'email' => NULL,
+          'recipient' => NULL,
+          'header' => NULL,
+          'footer' => NULL,
+          'message' => NULL,
+          'settings' => NULL,
+          'entity' => NULL,
+          'content' => NULL,
+          'view_mode' => NULL,
+          'email_format' => NULL,
+        ],
+      ],
+      'forward_link_formatter' => [
+        'variables' => [
+          'link' => NULL,
+        ],
+      ],
+    ];
+  }
+
+  /**
+   * Implements hook_theme_suggestions_HOOK().
+   */
+  #[Hook('theme_suggestions_forward')]
+  public function themeSuggestionsForward(array $variables) {
+    $suggestions = [];
+    $suggestions[] = 'forward__' . $variables['email_format'];
+    $suggestions[] = 'forward__' . $variables['email_format'] . '__' . $variables['view_mode'];
+    return $suggestions;
+  }
+
+  /**
+   * Implements hook_mail().
+   */
+  #[Hook('mail')]
+  public function mail($key, &$message, $params) {
+    $message['subject'] = $params['subject'];
+    $message['body'][] = $params['body'];
+  }
+
+  /**
+   * Implements hook_mail_alter().
+   */
+  #[Hook('mail_alter')]
+  public function mailAlter(&$message) {
+    if ($message['module'] == 'forward') {
+      if ($message['key'] == 'mail_entity' || $message['key'] == 'send_entity') {
+        if (!empty($message['params']['plain_text'])) {
+          $message['headers']['Content-Type'] = 'text/plain; charset=utf-8';
+        }
+        else {
+          $message['headers']['Content-Type'] = 'text/html; charset=utf-8';
+        }
+        $message['headers']['MIME-Version'] = '1.0';
+        $message['headers']['Reply-to'] = $message['params']['headers']['Reply-To'];
+        $message['headers']['Sender'] = $message['headers']['From'];
+      }
+    }
+  }
+
+}
diff --git a/src/Hook/ForwardTokensHooks.php b/src/Hook/ForwardTokensHooks.php
new file mode 100644
index 0000000..f65d79d
--- /dev/null
+++ b/src/Hook/ForwardTokensHooks.php
@@ -0,0 +1,145 @@
+<?php
+
+namespace Drupal\forward\Hook;
+
+use Drupal\Component\Utility\Xss;
+use Drupal\Core\Render\Markup;
+use Drupal\Core\Render\BubbleableMetadata;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for forward.
+ */
+class ForwardTokensHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_token_info().
+   */
+  #[Hook('token_info')]
+  public function tokenInfo() {
+    $data['types'] = [
+      'forward' => [
+        'name' => $this->t('Forward'),
+        'description' => $this->t('Tokens related to forward module.'),
+      ],
+    ];
+    $data['tokens'] = [
+      'forward' => [
+        'recipients' => [
+          'name' => $this->t("Email recipient(s)"),
+          'description' => $this->t("Recipient email address or email addresses for forwarded content."),
+        ],
+        'sender-name' => [
+          'name' => $this->t("Sender's name"),
+          'description' => $this->t("Name of sender as entered in Forwarding form."),
+        ],
+        'sender-email' => [
+          'name' => $this->t("Sender's email address"),
+          'description' => $this->t("Email address of sender as entered in Forwarding form."),
+        ],
+        'entity-type' => [
+          'name' => $this->t("Entity type"),
+          'description' => $this->t('The type of entity being forwarded. For nodes, uses the bundle name, e.g. "article" or "page", instead of the type.'),
+        ],
+        'entity-title' => [
+          'name' => $this->t("Entity title"),
+          'description' => $this->t('The title of the entity being forwarded.'),
+        ],
+        'entity-url' => [
+          'name' => $this->t('Entity URL'),
+          'description' => $this->t('The absolute URL for the entity being forwarded.'),
+        ],
+        'entity-link' => [
+          'name' => $this->t('Entity link'),
+          'description' => $this->t('A formatted link (using title and URL) of the entity being forwarded.'),
+        ],
+      ],
+    ];
+    return $data;
+  }
+
+  /**
+   * Implements hook_tokens().
+   */
+  #[Hook('tokens')]
+  public function tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
+    $replacements = [];
+    if ($type == 'forward' && !empty($data['forward'])) {
+      foreach ($tokens as $name => $original) {
+        switch ($name) {
+          case 'entity-type':
+            if (isset($data['forward']['entity'])) {
+              $entity = $data['forward']['entity'];
+              $entity_type = (string) $entity->getEntityType()->getLabel();
+              if ($entity->getEntityType()->hasKey('bundle')) {
+                // For entity types with bundles, use the bundle label
+                // instead of the entity type label.
+                $bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo($entity->getEntityTypeId());
+                $entity_type = $bundles[$entity->bundle()]['label'];
+              }
+              // Use the translated label, then lowercase it.
+              $entity_type = strtolower($this->t('@entity_type', [
+                '@entity_type' => $entity_type,
+              ], $options));
+              $replacements[$original] = Markup::create(Xss::filter($entity_type));
+            }
+            else {
+              $replacements[$original] = $this->t('page');
+            }
+            break;
+
+          case 'entity-title':
+            if (isset($data['forward']['entity'])) {
+              $entity = $data['forward']['entity'];
+              $entity_title = $entity->access('view') ? $entity->label() : $this->t('a page');
+              $replacements[$original] = Markup::create(Xss::filter($entity_title));
+            }
+            else {
+              $replacements[$original] = $this->t('a page');
+            }
+            break;
+
+          case 'entity-url':
+            if (isset($data['forward']['entity'])) {
+              $entity = $data['forward']['entity'];
+              $entity_url = $entity->access('view') ? $entity->toUrl('canonical', [
+                'absolute' => TRUE,
+              ])->toString() : $this->t('a URL');
+              $replacements[$original] = Markup::create(Xss::filter($entity_url));
+            }
+            else {
+              $replacements[$original] = $this->t('a URL');
+            }
+            break;
+
+          case 'entity-link':
+            if (isset($data['forward']['entity'])) {
+              $entity = $data['forward']['entity'];
+              $entity_link = $entity->access('view') ? $entity->toLink($entity->label())->toString() : $this->t('a link');
+              $replacements[$original] = Markup::create(Xss::filter($entity_link));
+            }
+            else {
+              $replacements[$original] = $this->t('a link');
+            }
+            break;
+
+          case 'recipients':
+            $replacements[$original] = isset($data['forward']['recipients']) ? Markup::create(Xss::filter(implode(', ', $data['forward']['recipients']))) : $this->t('(Recipient list)');
+            break;
+
+          case 'sender-name':
+            $replacements[$original] = isset($data['forward']['sender_name']) ? Markup::create(Xss::filter($data['forward']['sender_name'])) : $this->t('(Your name)');
+            break;
+
+          case 'sender-email':
+            $replacements[$original] = isset($data['forward']['sender_email']) ? Markup::create(Xss::filter($data['forward']['sender_email'])) : $this->t('(Your email address)');
+            break;
+        }
+      }
+    }
+    return $replacements;
+  }
+
+}
diff --git a/src/Hook/ForwardViewsHooks.php b/src/Hook/ForwardViewsHooks.php
new file mode 100644
index 0000000..93a83c1
--- /dev/null
+++ b/src/Hook/ForwardViewsHooks.php
@@ -0,0 +1,193 @@
+<?php
+
+namespace Drupal\forward\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for forward.
+ */
+class ForwardViewsHooks {
+  use StringTranslationTrait;
+  /**
+   * @file
+   * Provide views data.
+   */
+
+  /**
+   * Implements hook_views_data().
+   */
+  #[Hook('views_data')]
+  public function viewsData() {
+    $data = [];
+    // Forward statistics.
+    // Define the base group of this table. Fields that don't
+    // have a group defined will go into this field by default.
+    $data['forward_statistics']['table'] = [
+      'group' => $this->t('Forward statistics'),
+      'wizard_id' => 'forward_statistics',
+    ];
+    // For other entity base tables, explain how we join.
+    $entity_types = \Drupal::entityTypeManager()->getDefinitions();
+    foreach ($entity_types as $type => $info) {
+      if (is_a($info, 'Drupal\Core\Entity\ContentEntityType')) {
+        if ($info->getBaseTable()) {
+          $data['forward_statistics']['table']['join'][$info->getBaseTable() . '_field_data'] = [
+                // 'left_field' is the primary key in the referenced table.
+                // 'field' is the foreign key in this table.
+            'left_field' => $info->getKey('id'),
+            'field' => 'id',
+            'extra' => [
+                    [
+                      'field' => 'type',
+                      'value' => $type,
+                    ],
+            ],
+          ];
+        }
+      }
+    }
+    // Forward statistics fields.
+    $data['forward_statistics']['forward_count'] = [
+      'title' => $this->t('Forward count'),
+      'help' => $this->t('The number of times an entity was forwarded.'),
+      'field' => [
+        'id' => 'numeric',
+        'click sortable' => TRUE,
+      ],
+      'filter' => [
+        'id' => 'numeric',
+      ],
+      'sort' => [
+        'id' => 'standard',
+      ],
+    ];
+    $data['forward_statistics']['last_forward_timestamp'] = [
+      'title' => $this->t('Most recent forward'),
+      'help' => $this->t('The last time an entity was forwarded.'),
+      'field' => [
+        'id' => 'date',
+        'click sortable' => TRUE,
+      ],
+      'filter' => [
+        'id' => 'date',
+      ],
+      'sort' => [
+        'id' => 'standard',
+      ],
+    ];
+    // Forward log.
+    $data['forward_log']['table'] = [
+      'group' => $this->t('Forward log'),
+      'wizard_id' => 'forward_log',
+    ];
+    // Forward log fields.
+    $data['forward_log']['path'] = [
+      'title' => $this->t('Path'),
+      'help' => $this->t('The path that was forwarded.'),
+      'field' => [
+        'id' => 'standard',
+        'click sortable' => TRUE,
+      ],
+      'argument' => [
+        'id' => 'string',
+      ],
+      'filter' => [
+        'id' => 'standard',
+      ],
+      'sort' => [
+        'id' => 'standard',
+      ],
+    ];
+    $data['forward_log']['timestamp'] = [
+      'title' => $this->t('Timestamp'),
+      'help' => $this->t('The date and time of the forward.'),
+      'field' => [
+        'id' => 'date',
+        'click sortable' => TRUE,
+      ],
+      'filter' => [
+        'id' => 'date',
+      ],
+      'sort' => [
+        'id' => 'standard',
+      ],
+    ];
+    $data['forward_log']['uid'] = [
+      'title' => $this->t('UID'),
+      'help' => $this->t('The user ID of the user who forwarded.'),
+      'field' => [
+        'id' => 'numeric',
+      ],
+      'argument' => [
+        'id' => 'numeric',
+      ],
+      'filter' => [
+        'id' => 'numeric',
+      ],
+      'search' => [
+        'id' => 'standard',
+      ],
+      'relationship' => [
+        'title' => $this->t('User'),
+        'label' => $this->t('User referenced by Forward log'),
+        'help' => $this->t('The user that forwarded.'),
+        'base' => 'users',
+        'base field' => 'uid',
+        'id' => 'standard',
+      ],
+    ];
+    $data['forward_log']['hostname'] = [
+      'title' => $this->t('Hostname'),
+      'help' => $this->t('Hostname of the user who forwarded.'),
+      'field' => [
+        'id' => 'standard',
+      ],
+      'argument' => [
+        'id' => 'string',
+      ],
+      'filter' => [
+        'id' => 'standard',
+      ],
+      'sort' => [
+        'id' => 'standard',
+      ],
+    ];
+    return $data;
+  }
+
+  /**
+   * Implements hook_views_data_alter().
+   */
+  #[Hook('views_data_alter')]
+  public function viewsDataAlter(&$data) {
+    $entity_types = \Drupal::entityTypeManager()->getDefinitions();
+    foreach ($entity_types as $type => $info) {
+      if (is_a($info, 'Drupal\Core\Entity\ContentEntityType')) {
+        if ($info->getBaseTable()) {
+          // Add a relationship to the logs.
+          $data[$info->getBaseTable() . '_field_data']['forward_log'] = [
+            'title' => $this->t('Entity with forward logs'),
+            'help' => $this->t('Relate all forward logs for the entity. Creates a row for each instance the entity was forwarded.'),
+            'relationship' => [
+              'group' => $this->t('Forward log'),
+              'label' => $this->t('Forward log referenced by entity'),
+              'base' => 'forward_log',
+              'base field' => 'id',
+              'relationship field' => $info->getKey('id'),
+              'id' => 'standard',
+              'extra' => [
+                        [
+                          'field' => 'type',
+                          'value' => $type,
+                        ],
+              ],
+            ],
+          ];
+        }
+      }
+    }
+  }
+
+}
diff --git a/tests/modules/forward_test/forward_test.module b/tests/modules/forward_test/forward_test.module
index 58c5b2b..5433f70 100644
--- a/tests/modules/forward_test/forward_test.module
+++ b/tests/modules/forward_test/forward_test.module
@@ -5,6 +5,8 @@
  * Main module file for hooks related to testing Forward.
  */
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\forward_test\Hook\ForwardTestHooks;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
@@ -13,22 +15,15 @@ use Drupal\user\UserInterface;
 /**
  * Implements hook_help().
  */
+#[LegacyHook]
 function forward_test_help($route_name, RouteMatchInterface $route_match) {
-  switch ($route_name) {
-    case 'help.page.forward_test':
-      $output = '';
-      $output .= '<h3>' . t('About') . '</h3>';
-      $output .= '<p>' . t('This is a test help page for the forward_test module for the purpose of testing forward functionality.') . '</p>';
-      return $output;
-
-    default:
-  }
+  return \Drupal::service(ForwardTestHooks::class)->help($route_name, $route_match);
 }
 
 /**
  * Implements hook_forward_entity().
  */
+#[LegacyHook]
 function forward_test_forward_entity(UserInterface $account, EntityInterface $entity, FormStateInterface $form_state) {
-  // Example: redirect to the home page.
-  $form_state->setRedirect('<front>');
+  \Drupal::service(ForwardTestHooks::class)->forwardEntity($account, $entity, $form_state);
 }
diff --git a/tests/modules/forward_test/forward_test.services.yml b/tests/modules/forward_test/forward_test.services.yml
new file mode 100644
index 0000000..9ebbdd7
--- /dev/null
+++ b/tests/modules/forward_test/forward_test.services.yml
@@ -0,0 +1,5 @@
+
+services:
+  Drupal\forward_test\Hook\ForwardTestHooks:
+    class: Drupal\forward_test\Hook\ForwardTestHooks
+    autowire: true
diff --git a/tests/modules/forward_test/src/Hook/ForwardTestHooks.php b/tests/modules/forward_test/src/Hook/ForwardTestHooks.php
new file mode 100644
index 0000000..5fee898
--- /dev/null
+++ b/tests/modules/forward_test/src/Hook/ForwardTestHooks.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Drupal\forward_test\Hook;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\user\UserInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for forward_test.
+ */
+class ForwardTestHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_help().
+   */
+  #[Hook('help')]
+  public function help($route_name, RouteMatchInterface $route_match) {
+    switch ($route_name) {
+      case 'help.page.forward_test':
+        $output = '';
+        $output .= '<h3>' . $this->t('About') . '</h3>';
+        $output .= '<p>' . $this->t('This is a test help page for the forward_test module for the purpose of testing forward functionality.') . '</p>';
+        return $output;
+
+      default:
+    }
+  }
+
+  /**
+   * Implements hook_forward_entity().
+   */
+  #[Hook('forward_entity')]
+  public function forwardEntity(UserInterface $account, EntityInterface $entity, FormStateInterface $form_state) {
+    // Example: redirect to the home page.
+    $form_state->setRedirect('<front>');
+  }
+
+}
diff --git a/tests/src/Functional/ForwardFormTest.php b/tests/src/Functional/ForwardFormTest.php
index 6d91c8d..8f5eb6e 100644
--- a/tests/src/Functional/ForwardFormTest.php
+++ b/tests/src/Functional/ForwardFormTest.php
@@ -4,6 +4,8 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\forward\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Core\Test\AssertMailTrait;
 
 /**
@@ -11,6 +13,8 @@ use Drupal\Core\Test\AssertMailTrait;
  *
  * @group forward
  */
+#[Group('forward')]
+#[RunTestsInSeparateProcesses]
 class ForwardFormTest extends ForwardTestBase {
 
   use AssertMailTrait;
diff --git a/tests/src/Functional/PermissionsTest.php b/tests/src/Functional/PermissionsTest.php
index a52cbca..b3daa6d 100644
--- a/tests/src/Functional/PermissionsTest.php
+++ b/tests/src/Functional/PermissionsTest.php
@@ -4,11 +4,16 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\forward\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
+
 /**
  * Test the permissions.
  *
  * @group forward
  */
+#[Group('forward')]
+#[RunTestsInSeparateProcesses]
 class PermissionsTest extends ForwardTestBase {
 
   /**
