diff --git a/config_split.install b/config_split.install
index c54981d..90c2ce0 100644
--- a/config_split.install
+++ b/config_split.install
@@ -7,6 +7,8 @@
 
 declare(strict_types=1);
 
+use Drupal\Component\Utility\DeprecationHelper;
+
 /**
  * Enable the config_filter module.
  */
@@ -41,7 +43,7 @@ function config_split_update_8002() {
       unset($data[$list . '_skip_equal']);
     }
     $split->setData($data);
-    $split->save(TRUE);
+    DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => $split->save(), fn() => $split->save(TRUE));
   }
 }
 
@@ -69,6 +71,6 @@ function config_split_update_8003() {
     }
 
     $split->setData($data);
-    $split->save(TRUE);
+    DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => $split->save(), fn() => $split->save(TRUE));
   }
 }
diff --git a/config_split.module b/config_split.module
index 98bf870..2f4d6b5 100644
--- a/config_split.module
+++ b/config_split.module
@@ -5,111 +5,25 @@
  * Configuration split module functions.
  */
 
-use Drupal\Core\Config\Entity\ConfigEntityInterface;
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\config_split\Hook\ConfigSplitHooks;
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Link;
 use Drupal\Core\Routing\RouteMatchInterface;
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function config_split_form_config_admin_import_form_alter(&$form, FormStateInterface $form_state) {
-
-  $enabled = [];
-  $used_config_split_text = t('You are not using any config split configuration');
-  /** @var \Drupal\config_split\ConfigSplitManager $manager */
-  $manager = \Drupal::service('config_split.manager');
-  $sync = \Drupal::service('config.storage.sync');
-  $splits = $manager->loadMultiple($manager->listAll($sync), $sync);
-  foreach ($splits as $split) {
-    if ($split->get('status')) {
-      $entity = $manager->getSplitEntity($split->getName());
-      if ($entity instanceof ConfigEntityInterface) {
-        $enabled[] = Link::fromTextAndUrl($entity->label(), $entity->toUrl())->toString();
-      }
-      else {
-        $enabled[] = $split->get('label') . ' (new)';
-      }
-    }
-  }
-  if (!empty($enabled)) {
-    $used_config_split_text = t('Used config split configuration:') . ' ' . implode(', ', $enabled);
-  }
-
-  $form['config_split']['#weight'] = -10;
-  $form['config_split']['#markup'] = '<p>' . $used_config_split_text . '</p>';
-
+  \Drupal::service(ConfigSplitHooks::class)->formConfigAdminImportFormAlter($form, $form_state);
 }
 
 /**
  * Implements hook_help().
  */
+#[LegacyHook]
 function config_split_help($route_name, RouteMatchInterface $route_match) {
-  switch ($route_name) {
-    case 'help.page.config_split':
-      $output = '';
-      $output .= '<h3>' . t('About') . '</h3>';
-      $output .= '<p>' . t('
-The Drupal 8 configuration management works best when importing and exporting
-the whole set of the sites configuration. However, sometimes developers like to
-opt out of the robustness of CM and have a super-set of configuration active on
-their development machine. The canonical example for this is to have the
-<code>devel</code> module enabled or having a few block placements or views in
-the development environment and then not export them into the set of
-configuration to be deployed, yet still being able to share the development
-configuration with colleagues.
-
-This module allows to define sets of configuration that will get exported to
-separate directories when exporting, and get merged together when importing.
-It is possible to define in settings.php which of these sets should be active
-and considered for the export and import.
-
-For more information, see the <a href=":online-documentation">online documentation for the Config Split module</a>.',
-          [
-            ':online-documentation' => 'https://www.drupal.org/docs/8/modules/configuration-split',
-          ])
-        . '</p>';
-      $output .= '<h3>' . t('Uses') . '</h3>';
-      $output .= '<dl>';
-      $output .= '<dt>' . t('Setting up a split') . '</dt>';
-      $output .= '<dd>' . t('
-Let us assume that you configured your sync directory as follows:
-<code>
-$settings["config_sync_directory"] = \'../config/sync\';
-</code>
-Create a split with the folder `../config/my-split-folder` and create that
-directory. Now add a module that is currently active that you wish not to
-export, say `devel`. Next export all the configuration.
-This should have removed devel from `core.extensions` and moved the devel
-configuration to the split folder.') . '</dd>';
-      $output .= '<dt>' . t('Deploy splits') . '</dt>';
-      $output .= '<dd>' . t('
-Now deploy the configuration and devel will be un-installed.
-On another developers machine just import the configuration, add the override,
-clear the cache, and import again to have devel enabled on that environment.
-
-You should only edit active splits as inactive splits will not take effect when
-exporting the configuration.') . '</dd>';
-      $output .= '</dl>';
-
-      return $output;
-
-    case 'entity.config_split.add_form':
-    case 'entity.config_split.edit_form':
-      $output = '';
-      $output .= '<p>' . t('Config Split Help') . '</p>';
-      $output .= '<p>' . t('The configuration listed as part of a split
-      are exported to the split storage rather than the usual sync directory
-      when exporting the whole configuration. When importing the whole
-      configuration, the configuration in the split storages is merged with
-      the default sync directory and overrides the configuration.
-      The configuration does not end up being overwritten in the sense of
-      configuration overrides such as the overrides from settings.php.') . '</p>';
-
-      return $output;
-  }
-
-  return NULL;
+  return \Drupal::service(ConfigSplitHooks::class)->help($route_name, $route_match);
 }
 
 /**
@@ -118,12 +32,7 @@ function config_split_help($route_name, RouteMatchInterface $route_match) {
  * Automatically add any enabled database config splits' partial and complete
  * entries as whitelisted patterns for config_readonly.
  */
+#[LegacyHook]
 function config_split_config_readonly_whitelist_patterns() {
-  $patterns = [];
-  /** @var \Drupal\config_split\Entity\ConfigSplitEntityInterface[] $splits */
-  $splits = \Drupal::entityTypeManager()->getStorage('config_split')->loadByProperties(['storage' => 'database', 'status' => TRUE]);
-  foreach ($splits as $split) {
-    $patterns = array_merge($patterns, $split->get('partial_list'), $split->get('complete_list'));
-  }
-  return $patterns;
+  return \Drupal::service(ConfigSplitHooks::class)->configReadonlyWhitelistPatterns();
 }
diff --git a/config_split.services.yml b/config_split.services.yml
index db36555..670dd13 100644
--- a/config_split.services.yml
+++ b/config_split.services.yml
@@ -56,3 +56,7 @@ services:
       - "@state"
     tags:
       - { name: config.factory.override, priority: 5 }
+
+  Drupal\config_split\Hook\ConfigSplitHooks:
+    class: Drupal\config_split\Hook\ConfigSplitHooks
+    autowire: true
diff --git a/src/EventSubscriber/SplitImportExportSubscriber.php b/src/EventSubscriber/SplitImportExportSubscriber.php
index e067be1..0ac6759 100644
--- a/src/EventSubscriber/SplitImportExportSubscriber.php
+++ b/src/EventSubscriber/SplitImportExportSubscriber.php
@@ -45,7 +45,7 @@ public function __construct(ConfigSplitManager $manager, ConfigFactoryInterface
   /**
    * {@inheritdoc}
    */
-  public static function getSubscribedEvents() {
+  public static function getSubscribedEvents(): array {
     // We get the splits priority from the settings.
     // Unfortunately we can not load the existing splits from the drupal because
     // the subscribed events is compiled into the container in a compiler pass
diff --git a/src/Hook/ConfigSplitHooks.php b/src/Hook/ConfigSplitHooks.php
new file mode 100644
index 0000000..b099869
--- /dev/null
+++ b/src/Hook/ConfigSplitHooks.php
@@ -0,0 +1,133 @@
+<?php
+
+namespace Drupal\config_split\Hook;
+
+use Drupal\Core\Config\Entity\ConfigEntityInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Link;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+/**
+ * Hook implementations for config_split.
+ */
+class ConfigSplitHooks
+{
+    use StringTranslationTrait;
+    /**
+     * Implements hook_form_FORM_ID_alter().
+     */
+    #[Hook('form_config_admin_import_form_alter')]
+    public function formConfigAdminImportFormAlter(&$form, \Drupal\Core\Form\FormStateInterface $form_state)
+    {
+        $enabled = [
+        ];
+        $used_config_split_text = $this->t('You are not using any config split configuration');
+        /** @var \Drupal\config_split\ConfigSplitManager $manager */
+        $manager = \Drupal::service('config_split.manager');
+        $sync = \Drupal::service('config.storage.sync');
+        $splits = $manager->loadMultiple($manager->listAll($sync), $sync);
+        foreach ($splits as $split) {
+            if ($split->get('status')) {
+                $entity = $manager->getSplitEntity($split->getName());
+                if ($entity instanceof \Drupal\Core\Config\Entity\ConfigEntityInterface) {
+                    $enabled[] = \Drupal\Core\Link::fromTextAndUrl($entity->label(), $entity->toUrl())->toString();
+                } else {
+                    $enabled[] = $split->get('label') . ' (new)';
+                }
+            }
+        }
+        if (!empty($enabled)) {
+            $used_config_split_text = $this->t('Used config split configuration:') . ' ' . implode(', ', $enabled);
+        }
+        $form['config_split']['#weight'] = -10;
+        $form['config_split']['#markup'] = '<p>' . $used_config_split_text . '</p>';
+    }
+    /**
+     * Implements hook_help().
+     */
+    #[Hook('help')]
+    public function help($route_name, \Drupal\Core\Routing\RouteMatchInterface $route_match)
+    {
+        switch ($route_name) {
+            case 'help.page.config_split':
+                $output = '';
+                $output .= '<h3>' . $this->t('About') . '</h3>';
+                $output .= '<p>' . $this->t('
+The Drupal 8 configuration management works best when importing and exporting
+the whole set of the sites configuration. However, sometimes developers like to
+opt out of the robustness of CM and have a super-set of configuration active on
+their development machine. The canonical example for this is to have the
+<code>devel</code> module enabled or having a few block placements or views in
+the development environment and then not export them into the set of
+configuration to be deployed, yet still being able to share the development
+configuration with colleagues.
+
+This module allows to define sets of configuration that will get exported to
+separate directories when exporting, and get merged together when importing.
+It is possible to define in settings.php which of these sets should be active
+and considered for the export and import.
+
+For more information, see the <a href=":online-documentation">online documentation for the Config Split module</a>.', [
+                    ':online-documentation' => 'https://www.drupal.org/docs/8/modules/configuration-split',
+                ]) . '</p>';
+                $output .= '<h3>' . $this->t('Uses') . '</h3>';
+                $output .= '<dl>';
+                $output .= '<dt>' . $this->t('Setting up a split') . '</dt>';
+                $output .= '<dd>' . $this->t('
+Let us assume that you configured your sync directory as follows:
+<code>
+$settings["config_sync_directory"] = \'../config/sync\';
+</code>
+Create a split with the folder `../config/my-split-folder` and create that
+directory. Now add a module that is currently active that you wish not to
+export, say `devel`. Next export all the configuration.
+This should have removed devel from `core.extensions` and moved the devel
+configuration to the split folder.') . '</dd>';
+                $output .= '<dt>' . $this->t('Deploy splits') . '</dt>';
+                $output .= '<dd>' . $this->t('
+Now deploy the configuration and devel will be un-installed.
+On another developers machine just import the configuration, add the override,
+clear the cache, and import again to have devel enabled on that environment.
+
+You should only edit active splits as inactive splits will not take effect when
+exporting the configuration.') . '</dd>';
+                $output .= '</dl>';
+                return $output;
+            case 'entity.config_split.add_form':
+            case 'entity.config_split.edit_form':
+                $output = '';
+                $output .= '<p>' . $this->t('Config Split Help') . '</p>';
+                $output .= '<p>' . $this->t('The configuration listed as part of a split
+      are exported to the split storage rather than the usual sync directory
+      when exporting the whole configuration. When importing the whole
+      configuration, the configuration in the split storages is merged with
+      the default sync directory and overrides the configuration.
+      The configuration does not end up being overwritten in the sense of
+      configuration overrides such as the overrides from settings.php.') . '</p>';
+                return $output;
+        }
+        return NULL;
+    }
+    /**
+     * Implements hook_config_readonly_whitelist_patterns().
+     *
+     * Automatically add any enabled database config splits' partial and complete
+     * entries as whitelisted patterns for config_readonly.
+     */
+    #[Hook('config_readonly_whitelist_patterns')]
+    public static function configReadonlyWhitelistPatterns()
+    {
+        $patterns = [
+        ];
+        /** @var \Drupal\config_split\Entity\ConfigSplitEntityInterface[] $splits */
+        $splits = \Drupal::entityTypeManager()->getStorage('config_split')->loadByProperties([
+            'storage' => 'database',
+            'status' => TRUE,
+        ]);
+        foreach ($splits as $split) {
+            $patterns = array_merge($patterns, $split->get('partial_list'), $split->get('complete_list'));
+        }
+        return $patterns;
+    }
+}
diff --git a/tests/modules/config_split_sequence_test/config_split_sequence_test.module b/tests/modules/config_split_sequence_test/config_split_sequence_test.module
index 2227d99..b31f7a7 100644
--- a/tests/modules/config_split_sequence_test/config_split_sequence_test.module
+++ b/tests/modules/config_split_sequence_test/config_split_sequence_test.module
@@ -1,5 +1,12 @@
 <?php
 
+/**
+ * @file
+ */
+
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\config_split_sequence_test\Hook\ConfigSplitSequenceTestHooks;
+
 /**
  * @file
  * Hooks to test config splits sequence api.
@@ -8,13 +15,7 @@
 /**
  * Implements hook_config_schema_info_alter().
  */
+#[LegacyHook]
 function config_split_sequence_test_config_schema_info_alter(&$definitions) {
-  // This illustrates how to set a patch index callback. It works with any
-  // callable that can be serialized.
-  $definitions['config_split_sequence_test.nested_sequences']['mapping']['nested']['settings']['patch index'] = '0.0';
-  $definitions['config_split_sequence_test.nested_sequences']['mapping']['nested']['sequence']['settings']['patch index'] = '0';
-  // This is redundant because the third layer contains strings which will be
-  // used as values anyway. But this demonstrates that instead of a callable
-  // one can just specify "*" and it will take the value.
-  $definitions['config_split_sequence_test.nested_sequences']['mapping']['nested']['sequence']['sequence']['settings']['patch index'] = '*';
+  \Drupal::service(ConfigSplitSequenceTestHooks::class)->configSchemaInfoAlter($definitions);
 }
diff --git a/tests/modules/config_split_sequence_test/config_split_sequence_test.services.yml b/tests/modules/config_split_sequence_test/config_split_sequence_test.services.yml
new file mode 100644
index 0000000..d770a21
--- /dev/null
+++ b/tests/modules/config_split_sequence_test/config_split_sequence_test.services.yml
@@ -0,0 +1,5 @@
+
+services:
+  Drupal\config_split_sequence_test\Hook\ConfigSplitSequenceTestHooks:
+    class: Drupal\config_split_sequence_test\Hook\ConfigSplitSequenceTestHooks
+    autowire: true
diff --git a/tests/modules/config_split_sequence_test/src/Hook/ConfigSplitSequenceTestHooks.php b/tests/modules/config_split_sequence_test/src/Hook/ConfigSplitSequenceTestHooks.php
new file mode 100644
index 0000000..8ded595
--- /dev/null
+++ b/tests/modules/config_split_sequence_test/src/Hook/ConfigSplitSequenceTestHooks.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Drupal\config_split_sequence_test\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for config_split_sequence_test.
+ */
+class ConfigSplitSequenceTestHooks {
+  /**
+   * @file
+   * Hooks to test config splits sequence api.
+   */
+
+  /**
+   * Implements hook_config_schema_info_alter().
+   */
+  #[Hook('config_schema_info_alter')]
+  public static function configSchemaInfoAlter(&$definitions) {
+    // This illustrates how to set a patch index callback. It works with any
+    // callable that can be serialized.
+    $definitions['config_split_sequence_test.nested_sequences']['mapping']['nested']['settings']['patch index'] = '0.0';
+    $definitions['config_split_sequence_test.nested_sequences']['mapping']['nested']['sequence']['settings']['patch index'] = '0';
+    // This is redundant because the third layer contains strings which will be
+    // used as values anyway. But this demonstrates that instead of a callable
+    // one can just specify "*" and it will take the value.
+    $definitions['config_split_sequence_test.nested_sequences']['mapping']['nested']['sequence']['sequence']['settings']['patch index'] = '*';
+  }
+
+}
