diff --git a/.cspell-project-words.txt b/.cspell-project-words.txt
new file mode 100644
index 0000000000000000000000000000000000000000..83b4c22d44b8796922e31397672da56ca3e532b4
--- /dev/null
+++ b/.cspell-project-words.txt
@@ -0,0 +1,4 @@
+Persistor
+persistor
+persistors
+varchar
diff --git a/README.md b/README.md
index 99ad126ae5ec67df3cf545f95eeb82346c04b088..f0f9655e34e197586750e3d56441a6fc1f268247 100644
--- a/README.md
+++ b/README.md
@@ -1,37 +1,43 @@
-CONTENTS OF THIS FILE
----------------------
+## CONTENTS OF THIS FILE
 
- * Introduction
- * Features
- * Installation
- * Configuration
- * Usage
+- Introduction
+- Features
+- Installation
+- Configuration
+- Usage
 
+## INTRODUCTION
 
-INTRODUCTION
-------------
-This module checks GET and HTTP Request parameters (like utm_source, utm_medium, HTTP_REFERER) from anonymous visitors, and saves those in a cookie for further processing.
+This module checks GET and HTTP Request parameters (like utm_source, utm_medium,
+HTTP_REFERER) from anonymous visitors, and saves those in a cookie for further
+processing.
 
-When the visitor is going further on your website and is taking some actions like doing a purchase this information can be used for analytics. It makes initial paramaters persisistent for a session.
+When the visitor is going further on your website and is taking some actions
+like doing a purchase this information can be used for analytics. It makes
+initial parameters persistent for a session.
 
-FEATURES
-------------
-* Can read `GET` and `HTTP Request` parameters, and store them into cookie
-* Cookie lifetime is configurable (current session, custom, forever)
-* (configurable) Respects browser `DNT` Setting (Do-Not-Track)
-* Other modules can easily read cookie data using `persistent_visitor_parameters.cookie_manager` service
+## FEATURES
 
-INSTALLATION
-------------
+- Can read `GET` and `HTTP Request` parameters, and store them into cookie
+- Cookie lifetime is configurable (current session, custom, forever)
+- (configurable) Respects browser `DNT` Setting (Do-Not-Track)
+- Other modules can easily read cookie data using
+  `persistent_visitor_parameters.persistence_manager` service
 
-* Install as you would normally install a contributed Drupal module. Visit
-   https://www.drupal.org/node/1897420 for further information.
+## INSTALLATION
 
-CONFIGURATION
--------------
-* Configuration page is located here: `admin/config/persistent-visitor-parameters`
-* Configure parameters you would like to track, for example `utm_source`, `utm_medium` (GET) or `HTTP_REFERER` (HTTP Request) parameters 
+- Install as you would normally install a contributed Drupal module. Visit
+  https://www.drupal.org/node/1897420 for further information.
 
-USAGE
--------------
-* Read already saved cookies using `\Drupal::service('persistent_visitor_parameters.cookie_manager')->getCookie()` inside your module, and process this data further as you need it
+## CONFIGURATION
+
+- Configuration page is located here:
+  `admin/config/persistent-visitor-parameters`
+- Configure parameters you would like to track, for example `utm_source`,
+  `utm_medium` (GET) or `HTTP_REFERER` (HTTP Request) parameters
+
+## USAGE
+
+- Read already saved cookies using
+  `\Drupal::service('persistent_visitor_parameters.persistence_manager')->loadPersistedParameters()`
+  inside your module, and process this data further as you need it
diff --git a/config/install/persistent_visitor_parameters.settings.yml b/config/install/persistent_visitor_parameters.settings.yml
index b3abfef6c36f086ec91910a4b56e87e8cccd4bee..fcda0abf8f7653480eeb682dac6b1aa321aacfd9 100644
--- a/config/install/persistent_visitor_parameters.settings.yml
+++ b/config/install/persistent_visitor_parameters.settings.yml
@@ -1,7 +1,9 @@
 get_parameters: []
 server_parameters: []
 mode: 'first_touch'
-cookie_expire: 0
-dont_respect_dnt: false
+persistor: 'persistor_private_temp_store'
+persistor_key: 'pvp_stored_variables'
+persistor_expire: 0
+respect_dnt: true
 server_parameters_on_get_parameters_only: false
 allow_unsafe_deserialization: false
diff --git a/config/schema/persistent_visitor_parameters.schema.yml b/config/schema/persistent_visitor_parameters.schema.yml
index 42f1f0c232ec5f8aa1c2049f8aa1fb518d3dbb41..304dd8a10e8d10e0736291823d91dcc57f574953 100644
--- a/config/schema/persistent_visitor_parameters.schema.yml
+++ b/config/schema/persistent_visitor_parameters.schema.yml
@@ -5,14 +5,14 @@ persistent_visitor_parameters.settings:
     get_parameters:
       type: sequence
       label: 'List of GET parameters'
-      description: 'List GET parameters for tracking'
+      description: 'List GET parameters for tracking.'
       sequence:
         type: string
         name: 'Get parameter'
     server_parameters:
       type: sequence
       label: 'List of SERVER parameters'
-      description: 'List of SERVER parameters for tracking'
+      description: 'List of SERVER parameters for tracking.'
       sequence:
         type: string
         name: 'Server parameter'
@@ -20,14 +20,22 @@ persistent_visitor_parameters.settings:
       type: string
       label: 'Parameters save mode'
       description: 'Consider only very first-touch parameters, or allow it to be overridden with last-touch parameters?'
-    cookie_expire:
+    persistor:
+      type: string
+      label: 'Persistence storage method'
+      description: 'The persistence storage method: Cookie, Session, Private Temp Store.'
+    persistor_key:
+      type: string
+      label: 'Persistence storage key'
+      description: 'The persistence storage key / name to use for storing the values.'
+    persistor_expire:
       type: integer
-      label: 'Cookie expiration'
-      description: 'Cookie expiration mode: 0 for Session, 1 for Forever, 2 for Custom'
-    dont_respect_dnt:
+      label: 'Persistor expiration'
+      description: 'The persistor (cookie) expiration mode: 0: Session, 1: Forever, > 1: Custom (seconds).'
+    respect_dnt:
       type: boolean
-      label: 'Don not respect DNT?'
-      description: 'You can choose to do not respect visitors browser DNT setting (Do Not Track)'
+      label: 'Respect DNT?'
+      description: 'You can choose to do not respect visitors browser DNT setting (Do Not Track).'
     server_parameters_on_get_parameters_only:
       type: boolean
       label: 'Track with GET parameters only?'
@@ -35,4 +43,4 @@ persistent_visitor_parameters.settings:
     allow_unsafe_deserialization:
       type: boolean
       label: 'Allow unsafe unserialize?'
-      description: 'You can choose to allow unsafe unserialize of the cookie data'
+      description: 'You can choose to allow unsafe unserialize of the cookie data.'
diff --git a/modules/persistent_visitor_parameters_user_registration/config/install/views.view.user_registration_pvp_report.yml b/modules/persistent_visitor_parameters_user_registration/config/optional/views.view.user_registration_pvp_report.yml
similarity index 100%
rename from modules/persistent_visitor_parameters_user_registration/config/install/views.view.user_registration_pvp_report.yml
rename to modules/persistent_visitor_parameters_user_registration/config/optional/views.view.user_registration_pvp_report.yml
diff --git a/modules/persistent_visitor_parameters_user_registration/persistent_visitor_parameters_user_registration.install b/modules/persistent_visitor_parameters_user_registration/persistent_visitor_parameters_user_registration.install
index ac706d2421e93e9e0eb9e87beaacf9632765134b..9b0b02bcb8ff99bdf1b259fba46489c20aec6f43 100644
--- a/modules/persistent_visitor_parameters_user_registration/persistent_visitor_parameters_user_registration.install
+++ b/modules/persistent_visitor_parameters_user_registration/persistent_visitor_parameters_user_registration.install
@@ -74,7 +74,7 @@ function persistent_visitor_parameters_user_registration_update_10001() {
   // Add new "track_registration_url":
   \Drupal::configFactory()->getEditable('persistent_visitor_parameters_user_registration.settings')
     ->set('track_registration_url', TRUE)
-    ->set('track_http_referrer_fallback', TRUE)
+    ->set('track_current_route_fallback', TRUE)
     ->save();
 
   // Add database row "registration_url" inside
diff --git a/modules/persistent_visitor_parameters_user_registration/persistent_visitor_parameters_user_registration.module b/modules/persistent_visitor_parameters_user_registration/persistent_visitor_parameters_user_registration.module
index 74389165da90bff539566538eb1184a99267d925..ee5361b8bea06566a81a0692b8bf6333a22f9a62 100644
--- a/modules/persistent_visitor_parameters_user_registration/persistent_visitor_parameters_user_registration.module
+++ b/modules/persistent_visitor_parameters_user_registration/persistent_visitor_parameters_user_registration.module
@@ -104,11 +104,9 @@ function persistent_visitor_parameters_user_registration_user_insert($account) {
   if (!\Drupal::currentUser()->isAnonymous()) {
     return;
   }
-  // Retrieve the cookie manager service.
-  $cookieManager = \Drupal::service('persistent_visitor_parameters.cookie_manager');
-
-  // Get cookies using the service.
-  $cookieContent = $cookieManager->getCookie();
+  // Retrieve the persistence manager service.
+  $persistenceManager = \Drupal::service('persistent_visitor_parameters.persistence_manager');
+  $persistedParameters = $persistenceManager->loadPersistedParameters();
 
   // Initialize the fields array:
   $fieldsArray = [];
@@ -116,18 +114,20 @@ function persistent_visitor_parameters_user_registration_user_insert($account) {
   // If there is cookie content, filter out the UTM parameters and set them
   // on the fields array:
   $parametersToTrack = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'HTTP_REFERER'];
-  if (!empty($cookieContent)) {
-    $fieldsArray = array_filter($cookieContent, fn($key) => in_array($key, $parametersToTrack, TRUE), ARRAY_FILTER_USE_KEY);
+  if ($persistedParameters) {
+    $fieldsArray = array_filter($persistedParameters, fn($key) => in_array($key, $parametersToTrack, TRUE), ARRAY_FILTER_USE_KEY);
   }
-  // If the cookie is empty and fallback is enabled, track from there:
+  // If the cookie is empty, try to get the referrer from the server parameters:
   elseif ($config->get('track_current_route_fallback')) {
+    $fieldsArray['HTTP_REFERER'] = $currentRequest->server->get('HTTP_REFERER');
     $fieldsArray = array_filter($currentRequest->query->all(), fn($key) => in_array($key, $parametersToTrack, TRUE), ARRAY_FILTER_USE_KEY);
   }
-  // HTTP_REFERER is treated individually from the cookie to better have a value from URL than none:
+  // HTTP_REFERER is treated individually from the cookie to better have
+  // a value from URL than none:
   if (empty($fieldsArray['HTTP_REFERER']) && $config->get('track_current_route_fallback')) {
     $fieldsArray['HTTP_REFERER'] = $currentRequest->server->get('HTTP_REFERER');
   }
-    
+
   // Additionally if the registration URL tracking is enabled, add the current
   // URL to the fields array:
   $trackRegistrationUrl = $config->get('track_registration_url');
diff --git a/modules/persistent_visitor_parameters_user_registration/src/Form/SettingsForm.php b/modules/persistent_visitor_parameters_user_registration/src/Form/SettingsForm.php
index 5af76f778940adf316e5882170f9767e65a28bfd..35ab4e9d01b16103a953509894b01c0ab72c911f 100644
--- a/modules/persistent_visitor_parameters_user_registration/src/Form/SettingsForm.php
+++ b/modules/persistent_visitor_parameters_user_registration/src/Form/SettingsForm.php
@@ -52,7 +52,7 @@ final class SettingsForm extends ConfigFormBase {
     $form['track_current_route_fallback'] = [
       '#type' => 'checkbox',
       '#title' => $this->t('Track current request parameters as fallback'),
-      '#default_value' => $config->get('track_http_referrer_fallback'),
+      '#default_value' => $config->get('track_current_route_fallback'),
       '#description' => $this->t('When enabled, if the cookie content is empty (e.g. no cookie set), the utm parameters & "HTTP_REFERER" are being fetched from the current request.'),
       '#states' => [
         'visible' => [
@@ -70,7 +70,7 @@ final class SettingsForm extends ConfigFormBase {
     $this->config('persistent_visitor_parameters_user_registration.settings')
       ->set('enable_utm_user_logging', $form_state->getValue('enable_utm_user_logging'))
       ->set('track_registration_url', $form_state->getValue('track_registration_url'))
-      ->set('track_http_referrer_fallback', $form_state->getValue('track_current_route_fallback'))
+      ->set('track_current_route_fallback', $form_state->getValue('track_current_route_fallback'))
       ->save();
     parent::submitForm($form, $form_state);
   }
diff --git a/modules/persistent_visitor_parameters_user_registration/tests/src/Functional/GenericTest.php b/modules/persistent_visitor_parameters_user_registration/tests/src/Functional/GenericTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e2c16873f678dc5e29f5a996e6485dda062cd343
--- /dev/null
+++ b/modules/persistent_visitor_parameters_user_registration/tests/src/Functional/GenericTest.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Drupal\Tests\persistent_visitor_parameters_user_registration\Functional;
+
+use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
+
+/**
+ * Generic module test for persistent_visitor_parameters_user_registration.
+ *
+ * @group persistent_visitor_parameters_user_registration
+ */
+class GenericTest extends GenericModuleTestBase {
+
+  /**
+   * {@inheritDoc}
+   */
+  protected function assertHookHelp(string $module): void {
+    // Don't do anything here. Just overwrite this useless method, so we do
+    // don't have to implement hook_help().
+  }
+
+}
diff --git a/persistent_visitor_parameters.install b/persistent_visitor_parameters.install
index b0f4b6c63dfeaf4dc603a9d39892b97f42d0dd88..8087d45fb33b94b0f4cf0d447d7032919ef3d411 100644
--- a/persistent_visitor_parameters.install
+++ b/persistent_visitor_parameters.install
@@ -20,26 +20,49 @@ function persistent_visitor_parameters_update_10001() {
   $getParameters = !empty($config->get('get_parameters')) ? array_filter(array_map('trim', explode('|', $config->get('get_parameters')))) : [];
   $serverParameters = !empty($config->get('server_parameters')) ? array_filter(array_map('trim', explode('|', $config->get('server_parameters')))) : [];
   $mode = $config->get('mode') == 0 ? 'first_touch' : 'last_touch';
-  $cookieExpire = match ($config->get('cookie_expire')) {
+  $cookieExpire = match ($config->get('persistor_expire')) {
     '0' => 0,
     '1' => 2147483647,
-    '2' => $config->get('custom_expire'),
+    '2' => $config->get('persistor_expire_custom'),
   };
   $config->set('get_parameters', $getParameters)
     ->set('server_parameters', $serverParameters)
     ->set('mode', $mode)
-    ->set('cookie_expire', $cookieExpire)
+    ->set('persistor_expire', $cookieExpire)
     ->set('allow_unsafe_deserialization', TRUE)
-    ->clear('custom_expire')
+    ->clear('persistor_expire_custom')
     ->save();
 }
 
 /**
- * Clear unnecessary "custom_expire" config.
+ * Clear unnecessary "persistor_expire_custom" config.
  */
 function persistent_visitor_parameters_update_10002() {
   \Drupal::configFactory()->getEditable('persistent_visitor_parameters.settings')
     ->set('server_parameters_on_get_parameters_only', FALSE)
-    ->clear('custom_expire')
+    ->clear('persistor_expire_custom')
+    ->save();
+}
+
+/**
+ * Changed service definitions. Clear container cache.
+ */
+function persistent_visitor_parameters_update_10003() {
+  \Drupal::service('kernel')->rebuildContainer();
+}
+
+/**
+ * Introduce new persistence storage setting.
+ *
+ * You may now choose where to store
+ * the parameters persistently: Cookie (default), Session, Private Temp Store,
+ * disabled. Visit the settings page to adjust the setting to your preference.
+ */
+function persistent_visitor_parameters_update_10004() {
+  $config = \Drupal::configFactory()->getEditable('persistent_visitor_parameters.settings');
+  $config->set('persistor', 'persistor_cookie')
+    ->set('persistor_key', 'pvp_stored_variables')
+    ->set('persistor_expire', $config->get('persistor_expire'))
+    ->set('respect_dnt', empty($config->get('dont_respect_dnt')))
     ->save();
 }
diff --git a/persistent_visitor_parameters.links.menu.yml b/persistent_visitor_parameters.links.menu.yml
index ada260f697df05a3bea78d344ed28592e6fb3465..cd6ea9d7ed55aee9770e8de7166d2c39d2d8d702 100644
--- a/persistent_visitor_parameters.links.menu.yml
+++ b/persistent_visitor_parameters.links.menu.yml
@@ -8,3 +8,8 @@ persistent_visitor_parameters.settings:
   parent: 'persistent_visitor_parameters.overview'
   route_name: persistent_visitor_parameters.settings
   description: 'Configuration for the Persistent Visitor Parameters'
+persistent_visitor_parameters.debug:
+  title: 'Persistent Visitor Parameters Debug'
+  parent: 'persistent_visitor_parameters.overview'
+  route_name: persistent_visitor_parameters.debug
+  description: 'Debug the Persistent Visitor Parameters'
diff --git a/persistent_visitor_parameters.links.task.yml b/persistent_visitor_parameters.links.task.yml
index 3617df305f2c71f6275d4e0446d45915665df618..9d8903f602c5de701f55f1f3e49d920fc9258522 100644
--- a/persistent_visitor_parameters.links.task.yml
+++ b/persistent_visitor_parameters.links.task.yml
@@ -2,3 +2,8 @@ persistent_visitor_parameters.settings:
   title: Settings
   route_name: persistent_visitor_parameters.settings
   base_route: persistent_visitor_parameters.settings
+persistent_visitor_parameters.debug:
+  title: Debug
+  route_name: persistent_visitor_parameters.debug
+  base_route: persistent_visitor_parameters.settings
+  weight: 99
diff --git a/persistent_visitor_parameters.permissions.yml b/persistent_visitor_parameters.permissions.yml
index c18efd24f47cb42a8221b4298ac927d4baaf7e1e..f44343fc4a66f57794b629648ebbda3de621f0d5 100644
--- a/persistent_visitor_parameters.permissions.yml
+++ b/persistent_visitor_parameters.permissions.yml
@@ -1,3 +1,8 @@
 administer persistent_visitor_parameters configuration:
   title: 'Administer Persistent Visitor Parameters configuration'
   description: 'Allows the user to administer the Persistent Visitor Parameters configuration'
+  restrict access: true
+access persistent_visitor_parameters debug:
+  title: 'Access Persistent Visitor Parameters Debug page'
+  description: 'Allows the user to access the Persistent Visitor Parameters debug page'
+  restrict access: true
diff --git a/persistent_visitor_parameters.routing.yml b/persistent_visitor_parameters.routing.yml
index 42ad211a6a997e1e7737748a73a73f9f237b57e4..3464ba78ca337ba4523d5807fa8ee224fcd7cfc8 100644
--- a/persistent_visitor_parameters.routing.yml
+++ b/persistent_visitor_parameters.routing.yml
@@ -14,3 +14,11 @@ persistent_visitor_parameters.settings:
     _permission: 'administer persistent_visitor_parameters configuration'
   options:
     _admin_route: TRUE
+
+persistent_visitor_parameters.debug:
+  path: '/admin/config/people/persistent-visitor-parameters/debug'
+  defaults:
+    _title: 'Persistent Visitor Parameters Debug'
+    _controller: '\Drupal\persistent_visitor_parameters\Controller\PersistentVisitorParametersDebugController'
+  requirements:
+    _permission: 'access persistent_visitor_parameters debug'
diff --git a/persistent_visitor_parameters.services.yml b/persistent_visitor_parameters.services.yml
index 9b245b10fc257a85c65a0f147b9717e6dac76dd2..2c1a4b78c2542cd863bf02ebe71b549031887bd3 100644
--- a/persistent_visitor_parameters.services.yml
+++ b/persistent_visitor_parameters.services.yml
@@ -1,10 +1,36 @@
 services:
   persistent_visitor_parameters.response_subscriber:
     class: Drupal\persistent_visitor_parameters\EventSubscriber\ResponseSubscriber
-    arguments: ['@persistent_visitor_parameters.cookie_manager', '@current_user']
+    arguments:
+      ['@persistent_visitor_parameters.persistence_manager', '@current_user']
     tags:
-      - {name: event_subscriber}
+      - { name: event_subscriber }
 
-  persistent_visitor_parameters.cookie_manager:
-    class: Drupal\persistent_visitor_parameters\CookieManager
-    arguments: ['@request_stack', '@config.factory', '@datetime.time']
+  persistent_visitor_parameters.persistence_manager:
+    class: Drupal\persistent_visitor_parameters\PersistenceManager
+    arguments: ['@persistent_visitor_parameters.parameter_manager', '@config.factory']
+
+  persistent_visitor_parameters.parameter_manager:
+    class: Drupal\persistent_visitor_parameters\ParameterManager
+    arguments: ['@request_stack', '@config.factory']
+
+  persistent_visitor_parameters.persistor_base:
+    abstract: true
+    class: Drupal\persistent_visitor_parameters\PersistorBase
+    arguments: ['@config.factory']
+
+  persistent_visitor_parameters.persistor_cookie:
+    class: Drupal\persistent_visitor_parameters\PersistorCookie
+    arguments: ['@config.factory', '@request_stack', '@datetime.time']
+
+  persistent_visitor_parameters.persistor_session:
+    class: Drupal\persistent_visitor_parameters\PersistorSession
+    arguments: ['@config.factory', '@request_stack']
+
+  persistent_visitor_parameters.persistor_private_temp_store:
+    class: Drupal\persistent_visitor_parameters\PersistorPrivateTempStore
+    arguments: ['@config.factory', '@tempstore.private']
+
+  persistent_visitor_parameters.persistor_null:
+    class: Drupal\persistent_visitor_parameters\PersistorNull
+    arguments: ['@config.factory']
diff --git a/src/Constants/PersistentVisitorParametersConstants.php b/src/Constants/PersistentVisitorParametersConstants.php
deleted file mode 100644
index 5f5b8dd9418fbd3e8d20d6a928c51a134bb4c6fa..0000000000000000000000000000000000000000
--- a/src/Constants/PersistentVisitorParametersConstants.php
+++ /dev/null
@@ -1,13 +0,0 @@
-<?php
-
-namespace Drupal\persistent_visitor_parameters\Constants;
-
-/**
- * Constants for the Persistent Visitor Parameters module.
- */
-class PersistentVisitorParametersConstants {
-
-  const COOKIE_EXPIRE_SESSION = 0;
-  const COOKIE_EXPIRE_NEVER = 2147483647;
-
-}
diff --git a/src/Controller/PersistentVisitorParametersDebugController.php b/src/Controller/PersistentVisitorParametersDebugController.php
new file mode 100644
index 0000000000000000000000000000000000000000..d9d24b218d658ce01c45e058be1d9bbacdfa175e
--- /dev/null
+++ b/src/Controller/PersistentVisitorParametersDebugController.php
@@ -0,0 +1,53 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\persistent_visitor_parameters\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\persistent_visitor_parameters\PersistenceManager;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Returns responses for Persistent Visitor Parameters routes.
+ */
+final class PersistentVisitorParametersDebugController extends ControllerBase {
+
+  /**
+   * The controller constructor.
+   */
+  public function __construct(
+    private readonly PersistenceManager $persistenceManager,
+  ) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container): self {
+    return new self(
+      $container->get('persistent_visitor_parameters.persistence_manager'),
+    );
+  }
+
+  /**
+   * Builds the response.
+   */
+  public function __invoke(): array {
+    $persistedParameters = $this->persistenceManager->loadPersistedParameters();
+    $build['help'] = [
+      '#type' => 'item',
+      '#plain_text' => "This debug page shows the persistent visitor parameters currently present in this users storage.",
+    ];
+    $build['content'] = [
+      '#type' => 'inline_template',
+      // @todo Remove 2nd sentence, once we support authenticated users!
+      '#template' => "{% if output %}<pre>{{ output }}</pre>{% else %}{{'No persistent visitor parameters present (for the current user). <em>Authenticated users are not yet supported!</em>'|t}}{% endif %}",
+      '#context' => [
+        'output' => print_r($persistedParameters, TRUE),
+      ],
+    ];
+
+    return $build;
+  }
+
+}
diff --git a/src/CookieManager.php b/src/CookieManager.php
deleted file mode 100644
index 91d8621767043eb05196a7f32bfa1bda7f183f6f..0000000000000000000000000000000000000000
--- a/src/CookieManager.php
+++ /dev/null
@@ -1,148 +0,0 @@
-<?php
-
-namespace Drupal\persistent_visitor_parameters;
-
-use Drupal\Component\Datetime\TimeInterface;
-use Drupal\Component\Serialization\Json;
-use Drupal\Core\Config\ConfigFactoryInterface;
-use Drupal\Core\Config\ImmutableConfig;
-use Drupal\persistent_visitor_parameters\Constants\PersistentVisitorParametersConstants;
-use Symfony\Component\HttpFoundation\Cookie;
-use Symfony\Component\HttpFoundation\RequestStack;
-
-/**
- * The cookie manager service.
- */
-class CookieManager {
-
-  /**
-   * The name of the cookie used to store defined query parameters.
-   */
-  const COOKIE_NAME = 'pvp_stored_variables';
-
-  /**
-   * The configuration object.
-   *
-   * @var \Drupal\Core\Config\ImmutableConfig
-   */
-  protected ImmutableConfig $config;
-
-  /**
-   * Constructs a CookieManager object.
-   *
-   * @param \Symfony\Component\HttpFoundation\RequestStack $requestStack
-   *   The request stack.
-   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
-   *   The configuration factory.
-   * @param \Drupal\Component\Datetime\TimeInterface $time
-   *   The time service.
-   */
-  public function __construct(
-    protected RequestStack $requestStack,
-    ConfigFactoryInterface $configFactory,
-    protected TimeInterface $time,
-  ) {
-    $this->config = $configFactory->get('persistent_visitor_parameters.settings');
-  }
-
-  /**
-   * Sets a cookie with the necessary parameters.
-   *
-   * @param \Symfony\Component\HttpFoundation\Response $response
-   *   The response object.
-   */
-  public function setCookie($response) {
-    $serverParams = $this->neededServerParams();
-    $getParams = $this->neededGetParams();
-
-    // If we are tracking with GET parameters only, and there are no GET
-    // parameters, we can return early:
-    if ($this->config->get('server_parameters_on_get_parameters_only') && empty($getParams)) {
-      return;
-    }
-
-    $cookieContent = $getParams + $serverParams;
-
-    // If expected parameters are missing, we don't need to do anything.
-    if (empty($cookieContent)) {
-      return;
-    }
-
-    // Get the Parameter save mode.
-    $mode = $this->config->get('mode');
-
-    // If first-touch mode activated, and cookie already exists... skip the new
-    // one. Otherwise - replace it with a new one:
-    if ($mode === 'first_touch' && !empty($this->getCookie())) {
-      return;
-    }
-
-    $cookieExpire = $this->config->get('cookie_expire');
-
-    // If we are using custom cookie expiration, we need to add the current time
-    // to the expiration time, when setting the cookie:
-    if ($cookieExpire != PersistentVisitorParametersConstants::COOKIE_EXPIRE_SESSION && $cookieExpire != PersistentVisitorParametersConstants::COOKIE_EXPIRE_NEVER) {
-      $cookieExpire += $this->time->getRequestTime();
-    }
-
-    $cookie = Cookie::create(self::COOKIE_NAME, Json::encode($cookieContent), $this->config->get('cookie_expire'));
-    $response->headers->setCookie($cookie);
-  }
-
-  /**
-   * Retrieves the cookie content.
-   *
-   * @return array|null
-   *   The decoded cookie content or NULL if the cookie does not exist.
-   */
-  public function getCookie() {
-    $cookie = $this->requestStack->getCurrentRequest()->cookies->get(self::COOKIE_NAME);
-    if (empty($cookie)) {
-      return NULL;
-    }
-    $decodedCookie = Json::decode($cookie);
-    // @todo Remove this in next major release, no deprecation notice needed
-    // here, as it would spam the logs.
-    // If the decoding failed, $decodedCookie will be NULL or FALSE, so we try
-    // to unserialize the cookie content if the configuration allows it:
-    if ($this->config->get('allow_unsafe_deserialization') && empty($decodedCookie)) {
-      // @codingStandardsIgnoreStart
-      $decodedCookie = unserialize($cookie);
-      // @codingStandardsIgnoreEnd
-    }
-    return $decodedCookie;
-  }
-
-  /**
-   * Gets the necessary GET parameters from the request.
-   *
-   * @return array
-   *   An array of necessary GET parameters.
-   */
-  public function neededGetParams() {
-    $queryParams = $this->requestStack->getCurrentRequest()->query->all();
-    return array_intersect_key($queryParams, array_flip($this->config->get('get_parameters')));
-  }
-
-  /**
-   * Gets the necessary server parameters from the request.
-   *
-   * @return array
-   *   An array of necessary server parameters.
-   */
-  public function neededServerParams() {
-    $serverParams = $this->requestStack->getCurrentRequest()->server->all();
-    return array_intersect_key($serverParams, array_flip($this->config->get('server_parameters')));
-  }
-
-  /**
-   * Checks if the "Do Not Track" (DNT) header should be respected.
-   *
-   * @return bool
-   *   TRUE if DNT should not be respected, FALSE otherwise.
-   */
-  public function dontRespectDnt() {
-    return $this->config->get('dont_respect_dnt');
-  }
-
-}
diff --git a/src/EventSubscriber/ResponseSubscriber.php b/src/EventSubscriber/ResponseSubscriber.php
index db2157a9b9b457168219dc52b5d484b9497f0fd9..179478e2818d196e2dc223028e4d13c88724a519 100644
--- a/src/EventSubscriber/ResponseSubscriber.php
+++ b/src/EventSubscriber/ResponseSubscriber.php
@@ -5,7 +5,7 @@ namespace Drupal\persistent_visitor_parameters\EventSubscriber;
 use Symfony\Component\HttpKernel\Event\ResponseEvent;
 use Drupal\Core\Render\HtmlResponse;
 use Drupal\Core\Session\AccountInterface;
-use Drupal\persistent_visitor_parameters\CookieManager;
+use Drupal\persistent_visitor_parameters\PersistenceManager;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 use Symfony\Component\HttpKernel\KernelEvents;
 
@@ -17,20 +17,7 @@ class ResponseSubscriber implements EventSubscriberInterface {
   /**
    * {@inheritDoc}
    */
-  protected $cookieManager;
-
-  /**
-   * {@inheritDoc}
-   */
-  protected $currentUser;
-
-  /**
-   * {@inheritDoc}
-   */
-  public function __construct(CookieManager $cookie_manager, AccountInterface $current_user) {
-    $this->cookieManager = $cookie_manager;
-    $this->currentUser = $current_user;
-  }
+  public function __construct(protected PersistenceManager $persistenceManager, protected AccountInterface $currentUser) {}
 
   /**
    * {@inheritDoc}
@@ -41,6 +28,7 @@ class ResponseSubscriber implements EventSubscriberInterface {
       return;
     }
 
+    // Only handle main request:
     if (!$event->isMainRequest()) {
       return;
     }
@@ -51,17 +39,8 @@ class ResponseSubscriber implements EventSubscriberInterface {
       return;
     }
 
-    $request = $event->getRequest();
-
-    // Respect Do Not Track if not otherwise configured.
-    if (!$this->cookieManager->dontRespectDnt()) {
-      if ($request->server->get('HTTP_DNT')) {
-        return;
-      }
-    }
-
-    // Set the cookie on the current response:
-    $this->cookieManager->setCookie($response);
+    // Persist the current request parameters (if applies):
+    $this->persistenceManager->persistMatchingRequestParameters($response);
   }
 
   /**
diff --git a/src/Form/SettingsForm.php b/src/Form/SettingsForm.php
index a2b504a85d01f9bf7c443ed16c64cb4e2e0193e8..e2f0f877b9210b39079fe408c0078eb54ad08690 100644
--- a/src/Form/SettingsForm.php
+++ b/src/Form/SettingsForm.php
@@ -4,7 +4,7 @@ namespace Drupal\persistent_visitor_parameters\Form;
 
 use Drupal\Core\Form\ConfigFormBase;
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\persistent_visitor_parameters\Constants\PersistentVisitorParametersConstants;
+use Drupal\persistent_visitor_parameters\PersistorCookie;
 
 /**
  * The persistent_visitor_parameters settings form.
@@ -38,7 +38,7 @@ class SettingsForm extends ConfigFormBase {
     $form['get_parameters'] = [
       '#type' => 'textfield',
       '#title' => $this->t('List of GET parameters'),
-      '#description' => $this->t('List GET parameters for tracking, separated by "|". Example: "utm_source|utm_medium|utm_campaign|utm_content|utm_term"'),
+      '#description' => $this->t('List GET parameters for tracking, separated by "|". Example: "utm_source|utm_medium|utm_campaign|utm_content|utm_term". Leave empty to disable GET parameter tracking and persistence.'),
       '#default_value' => $getParametersDefaultValue,
     ];
 
@@ -47,7 +47,7 @@ class SettingsForm extends ConfigFormBase {
     $form['server_parameters'] = [
       '#type' => 'textfield',
       '#title' => $this->t('List of SERVER parameters'),
-      '#description' => $this->t('List of SERVER parameters for tracking, separated by "|". Example: "HTTP_REFERER|HTTP_USER_AGENT"'),
+      '#description' => $this->t('List of SERVER parameters for tracking, separated by "|". Example: "HTTP_REFERER|HTTP_USER_AGENT". Leave empty to disable SERVER parameter tracking and persistence.'),
       '#default_value' => $serverParametersDefaultValue,
     ];
 
@@ -76,49 +76,87 @@ class SettingsForm extends ConfigFormBase {
         'first_touch' => $this->t('First-touch'),
         'last_touch' => $this->t('Last-touch'),
       ],
+      '#required' => TRUE,
       '#default_value' => $config->get('mode'),
     ];
 
-    $cookieExpireDefaultValue = $config->get('cookie_expire');
-    if ($cookieExpireDefaultValue != PersistentVisitorParametersConstants::COOKIE_EXPIRE_SESSION && $cookieExpireDefaultValue != PersistentVisitorParametersConstants::COOKIE_EXPIRE_NEVER) {
+    $form['persistor'] = [
+      '#type' => 'radios',
+      '#title' => $this->t('Select persistence storage method'),
+      '#description' => nl2br($this->t("Select the store where you want to persist the data. Each store has pro's and cons:\n
+        <strong>Private Temp Store:</strong> @todo\n
+        <strong>Session:</strong> @todo\n
+        <strong>Cookie:</strong> @todo\n
+        <strong>Disabled:</strong> @todo\n")),
+      '#options' => [
+        'persistor_private_temp_store' => $this->t('Private Temp Store'),
+        'persistor_session' => $this->t('Session'),
+        'persistor_cookie' => $this->t('Cookie'),
+        'persistor_null' => $this->t('Disabled: Do not store persistently'),
+      ],
+      '#required' => TRUE,
+      '#default_value' => $config->get('persistor'),
+    ];
+
+    $form['persistor_key'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Persistence key / Cookie name'),
+      '#description' => $this->t('The key to use for storing the parameters persistently, based on the selected persistence method, e.g. Session value key, Private Temp Store key, cookie name. Warning: Changing this key will reset persistent values stored.'),
+      '#default_value' => $config->get('persistor_key'),
+      '#required' => TRUE,
+      '#states' => [
+        'visible' => [
+          ':input[name="persistor"]' => ['!value' => ''],
+        ],
+      ],
+    ];
+
+    $cookieExpireDefaultValue = $config->get('persistor_expire');
+    if ($cookieExpireDefaultValue != PersistorCookie::COOKIE_EXPIRE_SESSION && $cookieExpireDefaultValue != PersistorCookie::COOKIE_EXPIRE_NEVER) {
       $cookieExpireDefaultValue = 'custom';
     }
-    $form['cookie_expire'] = [
+    $form['persistor_expire'] = [
       '#type' => 'radios',
       '#title' => $this->t('Cookie expiration'),
       '#options' => [
-        PersistentVisitorParametersConstants::COOKIE_EXPIRE_SESSION => $this->t('Session'),
-        PersistentVisitorParametersConstants::COOKIE_EXPIRE_NEVER => $this->t('Never'),
+        PersistorCookie::COOKIE_EXPIRE_SESSION => $this->t('Session'),
+        PersistorCookie::COOKIE_EXPIRE_NEVER => $this->t('Never'),
         'custom' => $this->t('Custom'),
       ],
+      '#required' => TRUE,
       '#default_value' => $cookieExpireDefaultValue,
+      '#states' => [
+        'visible' => [
+          ':input[name="persistor"]' => ['value' => 'cookie'],
+        ],
+      ],
     ];
-    $form['custom_expire'] = [
+    $form['persistor_expire_custom'] = [
       '#type' => 'number',
       '#title' => $this->t('Custom duration'),
       '#description' => $this->t('The time the cookie expires. This is the number of seconds from the current time.'),
-      '#default_value' => $config->get('cookie_expire'),
+      '#default_value' => $config->get('persistor_expire'),
       '#states' => [
         'visible' => [
-          ':input[name="cookie_expire"]' => ['value' => 'custom'],
+          ':input[name="persistor_expire"]' => ['value' => 'custom'],
         ],
         'required' => [
-          ':input[name="cookie_expire"]' => ['value' => 'custom'],
+          ':input[name="persistor_expire"]' => ['value' => 'custom'],
         ],
       ],
     ];
 
-    $form['dont_respect_dnt'] = [
+    $form['respect_dnt'] = [
       '#type' => 'checkbox',
-      '#title' => $this->t("Don't respect DNT?"),
-      '#description' => $this->t('You can choose to do not respect visitors browser DNT setting (Do Not Track)'),
-      '#default_value' => $config->get('dont_respect_dnt'),
+      '#title' => $this->t("Respect DNT?"),
+      '#description' => $this->t('Respect DNT (Do Not Track) and do not persist the user data if true.'),
+      '#default_value' => $config->get('respect_dnt'),
     ];
 
     $form['allow_unsafe_deserialization'] = [
       '#type' => 'checkbox',
-      '#title' => $this->t('Allow unsafe deserialization [DEPRECATED]'),
-      '#description' => $this->t('Cookie content used to be serialized instead of JSON encoded. Enable it, to allow unserializing cookie content if it is not valid json. Be careful with this option, as it may lead to security vulnerabilities. Support for serialization will be removed in the next major release (2.x).'),
+      '#title' => $this->t('Allow unsafe deserialization [LEGACY]'),
+      '#description' => $this->t('Cookie content used to be PHP-serialized instead of JSON encoded. Enable it, to allow unserializing cookie content if it is not valid json (legacy fallback). Be careful with this option, as it may lead to security vulnerabilities. Support for serialization will be removed in the next major release (2.x).'),
       '#default_value' => $config->get('allow_unsafe_deserialization'),
     ];
 
@@ -131,17 +169,19 @@ class SettingsForm extends ConfigFormBase {
   public function submitForm(array &$form, FormStateInterface $form_state) {
     parent::submitForm($form, $form_state);
 
-    $cookieExpireValue = $form_state->getValue('cookie_expire');
+    $cookieExpireValue = $form_state->getValue('persistor_expire');
     if ($cookieExpireValue == 'custom') {
-      $cookieExpireValue = $form_state->getValue('custom_expire');
+      $cookieExpireValue = $form_state->getValue('persistor_expire_custom');
     }
 
     $this->config('persistent_visitor_parameters.settings')
       ->set('get_parameters', !empty($form_state->getValue('get_parameters')) ? array_filter(array_map('trim', explode('|', $form_state->getValue('get_parameters')))) : [])
       ->set('server_parameters', !empty($form_state->getValue('server_parameters')) ? array_filter(array_map('trim', explode('|', $form_state->getValue('server_parameters')))) : [])
       ->set('mode', $form_state->getValue('mode'))
-      ->set('cookie_expire', $cookieExpireValue)
-      ->set('dont_respect_dnt', $form_state->getValue('dont_respect_dnt'))
+      ->set('persistor', $form_state->getValue('persistor'))
+      ->set('persistor_key', $form_state->getValue('persistor_key'))
+      ->set('persistor_expire', $cookieExpireValue)
+      ->set('respect_dnt', $form_state->getValue('respect_dnt'))
       ->set('server_parameters_on_get_parameters_only', $form_state->getValue('server_parameters_on_get_parameters_only'))
       ->set('allow_unsafe_deserialization', $form_state->getValue('allow_unsafe_deserialization'))
       ->save();
diff --git a/src/ParameterManager.php b/src/ParameterManager.php
new file mode 100644
index 0000000000000000000000000000000000000000..2fee1c2619a1629af712a26145ab125acca057ed
--- /dev/null
+++ b/src/ParameterManager.php
@@ -0,0 +1,70 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\persistent_visitor_parameters;
+
+use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Config\ImmutableConfig;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * The parameter get manager.
+ *
+ * Handles the parameters from GET / SERVER.
+ */
+final class ParameterManager {
+
+  /**
+   * The configuration object.
+   *
+   * @var \Drupal\Core\Config\ImmutableConfig
+   */
+  protected ImmutableConfig $config;
+
+  /**
+   * Constructs a ParameterManager object.
+   */
+  public function __construct(
+    protected readonly RequestStack $requestStack,
+    protected readonly ConfigFactoryInterface $configFactory,
+  ) {
+    $this->config = $configFactory->get('persistent_visitor_parameters.settings');
+  }
+
+  /**
+   * Returns true if the current request has DNT header and respect_dnt active.
+   *
+   * @return bool
+   *   Should not be tracked, as DNT is active!
+   */
+  public function dntApplies(): bool {
+    // Respect Do Not Track if not otherwise configured.
+    return $this->requestStack->getCurrentRequest()->server->get('HTTP_DNT') && $this->config->get('respect_dnt');
+  }
+
+  /**
+   * Gets the selected GET parameters from the request.
+   *
+   * @return array
+   *   An array of selected GET parameters.
+   */
+  public function getMatchingGetParameters(): array {
+    $query = $this->requestStack->getCurrentRequest()->query->all();
+    $queryParams = UrlHelper::filterQueryParameters($query);
+    return array_intersect_key($queryParams, array_flip($this->config->get('get_parameters')));
+  }
+
+  /**
+   * Gets the selected server parameters from the request.
+   *
+   * @return array
+   *   An array of selected server parameters.
+   */
+  public function getMatchingServerParameters(): array {
+    $serverParams = $this->requestStack->getCurrentRequest()->server->all();
+    return array_intersect_key($serverParams, array_flip($this->config->get('server_parameters')));
+  }
+
+}
diff --git a/src/PersistenceManager.php b/src/PersistenceManager.php
new file mode 100644
index 0000000000000000000000000000000000000000..e9301d10de78e2df2ed70e3b865bb5ce18258a2f
--- /dev/null
+++ b/src/PersistenceManager.php
@@ -0,0 +1,117 @@
+<?php
+
+namespace Drupal\persistent_visitor_parameters;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Config\ImmutableConfig;
+use Drupal\Core\Render\HtmlResponse;
+
+/**
+ * The cookie manager service.
+ */
+class PersistenceManager {
+
+  /**
+   * The configuration object.
+   *
+   * @var \Drupal\Core\Config\ImmutableConfig
+   */
+  protected ImmutableConfig $config;
+
+  /**
+   * Constructs a CookieManager object.
+   *
+   * @param ParameterManager $parameterManager
+   *   The parameter manager.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
+   *   The config factory.
+   */
+  public function __construct(
+    protected ParameterManager $parameterManager,
+    ConfigFactoryInterface $configFactory,
+  ) {
+    $this->config = $configFactory->get('persistent_visitor_parameters.settings');
+  }
+
+  /**
+   * Helper method to return the parameters data array.
+   *
+   * @return array|null
+   *   The current request parameters array
+   */
+  protected function getMatchingRequestParameters(): ?array {
+    // If this request has DNT enabled and we respect DNT, return early:
+    if ($this->parameterManager->dntApplies()) {
+      return NULL;
+    }
+
+    $getParameters = $this->parameterManager->getMatchingGetParameters();
+    $serverParameters = $this->parameterManager->getMatchingServerParameters();
+
+    // If we are tracking with GET parameters only, and there are no GET
+    // parameters, we can return early:
+    if ($this->config->get('server_parameters_on_get_parameters_only') && empty($getParameters)) {
+      return NULL;
+    }
+    $data = array_merge($getParameters, $serverParameters);
+
+    // If expected parameters are missing, we return NULL:
+    if (empty($data)) {
+      return NULL;
+    }
+    return $data;
+  }
+
+  /**
+   * Stores the current request parameters, if matching.
+   *
+   * @param \Drupal\Core\Render\HtmlResponse $response
+   *   The current request response.
+   *
+   * @return bool
+   *   TRUE if parameters were persisted, else FALSE.
+   */
+  public function persistMatchingRequestParameters(HtmlResponse $response): bool {
+    $data = $this->getMatchingRequestParameters();
+
+    // If data is empty, we can return early:
+    if (empty($data)) {
+      return FALSE;
+    }
+
+    // If first-touch mode activated, and cookie already exists... skip the new
+    // one. Otherwise - replace it with a new one:
+    if ($this->config->get('mode') === 'first_touch' && !empty($this->loadPersistedParameters($response))) {
+      return FALSE;
+    }
+
+    return $this->getPersistor()->persist($data, $response);
+  }
+
+  /**
+   * Retrieves the cookie content.
+   *
+   * @return array|null
+   *   The decoded cookie content or NULL if the cookie does not exist.
+   */
+  public function loadPersistedParameters(): ?array {
+    return $this->getPersistor()->load();
+  }
+
+  /**
+   * Returns the persistor selected in config.
+   *
+   * @return PersistorInterface
+   *   The persistor to use.
+   */
+  public function getPersistor(): PersistorInterface {
+    $selectedPersistorService = $this->config->get('persistor');
+    // We dynamically load the service here, DI doesn't make much sense:
+    // phpcs:disable
+    // @phpstan-ignore-next-line
+    $persistor = \Drupal::service("persistent_visitor_parameters.{$selectedPersistorService}");
+    // phpcs:enable
+    return $persistor;
+  }
+
+}
diff --git a/src/PersistorBase.php b/src/PersistorBase.php
new file mode 100644
index 0000000000000000000000000000000000000000..d1a647affe1491540bd2c42350f504ca34252909
--- /dev/null
+++ b/src/PersistorBase.php
@@ -0,0 +1,41 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\persistent_visitor_parameters;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Config\ImmutableConfig;
+
+/**
+ * The persistor base class.
+ */
+abstract class PersistorBase implements PersistorInterface {
+
+  /**
+   * The configuration object.
+   *
+   * @var \Drupal\Core\Config\ImmutableConfig
+   */
+  protected ImmutableConfig $config;
+
+  /**
+   * Constructs a StoreBase object.
+   */
+  public function __construct(
+    protected readonly ConfigFactoryInterface $configFactory,
+  ) {
+    $this->config = $configFactory->get('persistent_visitor_parameters.settings');
+  }
+
+  /**
+   * Returns the key / name to use for persisting.
+   *
+   * @return string
+   *   The key.
+   */
+  public function getPersistorKey(): string {
+    return $this->config->get('persistor_key');
+  }
+
+}
diff --git a/src/PersistorCookie.php b/src/PersistorCookie.php
new file mode 100644
index 0000000000000000000000000000000000000000..36ffa9d975cc52fd059c54a82124326afe782cd5
--- /dev/null
+++ b/src/PersistorCookie.php
@@ -0,0 +1,90 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\persistent_visitor_parameters;
+
+use Drupal\Component\Datetime\TimeInterface;
+use Drupal\Component\Serialization\Json;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Render\HtmlResponse;
+use Symfony\Component\HttpFoundation\Cookie;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * The cookie persistor class.
+ */
+class PersistorCookie extends PersistorBase implements PersistorInterface {
+
+  const COOKIE_EXPIRE_SESSION = 0;
+  const COOKIE_EXPIRE_NEVER = 2147483647;
+
+  /**
+   * The constructor.
+   *
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
+   *   The config factors.
+   * @param \Symfony\Component\HttpFoundation\RequestStack $requestStack
+   *   The request stack.
+   * @param \Drupal\Component\Datetime\TimeInterface $datetimeTime
+   *   The TimeInterface.
+   */
+  public function __construct(
+    protected readonly ConfigFactoryInterface $configFactory,
+    protected readonly RequestStack $requestStack,
+    protected readonly TimeInterface $datetimeTime,
+  ) {
+    $this->config = $configFactory->get('persistent_visitor_parameters.settings');
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public function clear(): bool {
+    $this->requestStack->getCurrentRequest()->cookies->remove($this->getPersistorKey());
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function persist(array $data, ?HtmlResponse $response): bool {
+    $cookieExpire = $this->config->get('persistor_expire');
+
+    // If we are using custom cookie expiration, we need to add the current time
+    // to the expiration time, when setting the cookie:
+    if ($cookieExpire != self::COOKIE_EXPIRE_SESSION && $cookieExpire != self::COOKIE_EXPIRE_NEVER) {
+      $cookieExpire += $this->datetimeTime->getRequestTime();
+    }
+
+    $persistorKey = $this->getPersistorKey();
+    $cookie = Cookie::create($persistorKey, Json::encode($data), $cookieExpire);
+    $response->headers->setCookie($cookie);
+
+    return !empty($response->headers->getCookies()[$persistorKey]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function load(): ?array {
+    $cookie = $this->requestStack->getCurrentRequest()->cookies->get($this->getPersistorKey());
+    if (empty($cookie)) {
+      return NULL;
+    }
+    $decodedCookie = Json::decode($cookie);
+
+    // @deprecated 2.1.0:
+    // @todo Remove this in next major release, no deprecation notice needed
+    // here, as it would spam the logs.
+    // If the decoding failed, $decodedCookie will be NULL or FALSE, so we try
+    // to unserialize the cookie content if the configuration allows it:
+    if ($this->config->get('allow_unsafe_deserialization') && empty($decodedCookie)) {
+      // @codingStandardsIgnoreStart
+      $decodedCookie = unserialize($cookie);
+      // @codingStandardsIgnoreEnd
+    }
+    return $decodedCookie;
+  }
+
+}
diff --git a/src/PersistorInterface.php b/src/PersistorInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..6b9ec81e130056342fe3fb09948ef40b2eefabe9
--- /dev/null
+++ b/src/PersistorInterface.php
@@ -0,0 +1,47 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\persistent_visitor_parameters;
+
+use Drupal\Core\Render\HtmlResponse;
+
+/**
+ * The persistor interface.
+ */
+interface PersistorInterface {
+
+  /**
+   * Stores the data to persist.
+   *
+   * @param array $data
+   *   The data to persist.
+   * @param \Drupal\Core\Render\HtmlResponse $response
+   *   The HTML response. * Required for some of the persistors to modify the
+   *   current response.
+   *   @todo We should try to get rid of this parameter that is only
+   *   required for Cookies. Maybe we can instead get the response
+   *   somewhere from the current request?
+   *
+   * @return bool
+   *   True if something was persisted, false otherwise.
+   */
+  public function persist(array $data, ?HtmlResponse $response): bool;
+
+  /**
+   * Returns the persisted values as array, NULL if not existing.
+   *
+   * @return array|null
+   *   Array of the persisted values, if present, else NULL.
+   */
+  public function load(): ?array;
+
+  /**
+   * Clears the storage (if supported).
+   *
+   * @return bool
+   *   TRUE if successful, FALSE if not or not supported.
+   */
+  public function clear(): bool;
+
+}
diff --git a/src/PersistorNull.php b/src/PersistorNull.php
new file mode 100644
index 0000000000000000000000000000000000000000..72a5794f888667090aa0fdb1a78e7ad217f2d137
--- /dev/null
+++ b/src/PersistorNull.php
@@ -0,0 +1,37 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\persistent_visitor_parameters;
+
+use Drupal\Core\Render\HtmlResponse;
+
+/**
+ * The NULL persistor. Doesn't store or load anything.
+ */
+class PersistorNull extends PersistorBase implements PersistorInterface {
+
+  /**
+   * {@inheritDoc}
+   */
+  public function clear(): bool {
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function persist(array $data, ?HtmlResponse $response): bool {
+    // For this storage it's a success to not persist anything ;).
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function load(): ?array {
+    // Return nothing.
+    return NULL;
+  }
+
+}
diff --git a/src/PersistorPrivateTempStore.php b/src/PersistorPrivateTempStore.php
new file mode 100644
index 0000000000000000000000000000000000000000..0002dc22e68ea497795951125722c4cbe468af7f
--- /dev/null
+++ b/src/PersistorPrivateTempStore.php
@@ -0,0 +1,63 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\persistent_visitor_parameters;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Render\HtmlResponse;
+use Drupal\Core\TempStore\PrivateTempStore;
+use Drupal\Core\TempStore\PrivateTempStoreFactory;
+
+/**
+ * The private temp store persistor.
+ */
+class PersistorPrivateTempStore extends PersistorBase implements PersistorInterface {
+
+  /**
+   * The Private Temp Store.
+   *
+   * @var \Drupal\Core\TempStore\PrivateTempStore
+   */
+  protected PrivateTempStore $tempStore;
+
+  /**
+   * The private temp store.
+   *
+   * @var \Drupal\Core\TempStore\PrivateTempStore
+   */
+  public function __construct(
+    protected readonly ConfigFactoryInterface $configFactory,
+    PrivateTempStoreFactory $tempStoreFactory,
+  ) {
+    $this->tempStore = $tempStoreFactory->get('persistent_visitor_parameters');
+    $this->config = $configFactory->get('persistent_visitor_parameters.settings');
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public function clear(): bool {
+    $this->tempStore->delete($this->getPersistorKey());
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function persist(array $data, ?HtmlResponse $response): bool {
+    // Note: For anonymous users this value will only last within the active
+    // session. Values are lost as soon as the user navigates away.
+    $this->tempStore->set($this->getPersistorKey(), $data);
+    return !empty($this->tempStore->get($this->getPersistorKey()));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function load(): ?array {
+    $data = $this->tempStore->get($this->getPersistorKey());
+    return $data;
+  }
+
+}
diff --git a/src/PersistorSession.php b/src/PersistorSession.php
new file mode 100644
index 0000000000000000000000000000000000000000..6f86b8d32b5fc9d7f87e599244187c708b1f8457
--- /dev/null
+++ b/src/PersistorSession.php
@@ -0,0 +1,51 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\persistent_visitor_parameters;
+
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Render\HtmlResponse;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * The session persistor.
+ */
+class PersistorSession extends PersistorBase implements PersistorInterface {
+
+  public function __construct(
+    protected readonly ConfigFactoryInterface $configFactory,
+    protected readonly RequestStack $requestStack,
+  ) {
+    $this->config = $configFactory->get('persistent_visitor_parameters.settings');
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public function clear(): bool {
+    $session = $this->requestStack->getSession();
+    $session->remove($this->getPersistorKey());
+    $session->save();
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function persist(array $data, ?HtmlResponse $response): bool {
+    $session = $this->requestStack->getSession();
+    $session->set($this->getPersistorKey(), $data);
+    $session->save();
+    return !empty($session->get($this->getPersistorKey()));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function load(): ?array {
+    $session = $this->requestStack->getSession();
+    return $session->get($this->getPersistorKey());
+  }
+
+}
diff --git a/tests/src/Functional/FormFunctionalTest.php b/tests/src/Functional/FormFunctionalTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..17e7338591f3a6a1cd581715d6a9b2c1b4610b25
--- /dev/null
+++ b/tests/src/Functional/FormFunctionalTest.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Drupal\Tests\persistent_visitor_parameters\Functional;
+
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * This class provides methods for testing persistent_visitor_parameters.
+ *
+ * @group persistent_visitor_parameters
+ */
+class FormFunctionalTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'test_page_test',
+    'persistent_visitor_parameters',
+  ];
+
+  /**
+   * A user with authenticated permissions.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $user;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * The cookie manager service.
+   *
+   * @var \Drupal\persistent_visitor_parameters\PersistenceManager
+   */
+  protected $persistenceManager;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    $this->config('system.site')->set('page.front', '/test-page')->save();
+    $this->user = $this->drupalCreateUser([]);
+    $this->persistenceManager = \Drupal::service('persistent_visitor_parameters.persistence_manager');
+  }
+
+  /**
+   * Test if the cookie won't get set for admin users.
+   */
+  public function testPersistedParametersAdmin() {
+    $this->drupalLogin($this->rootUser);
+    $session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+    $this->drupalGet('/admin/config/people/persistent-visitor-parameters/settings');
+    $page->fillField('get_parameters', 'utm_source|utm_medium|utm_campaign|utm_term|utm_content');
+    $page->fillField('server_parameters', 'HTTP_REFERER|HTTP_USER_AGENT');
+    $page->pressButton('Save configuration');
+    $session->pageTextContains('The configuration options have been saved.');
+
+    // @todo check the value after save and also test all other values.
+  }
+
+}
diff --git a/tests/src/Functional/GenericPersistentVisitorParametersTest.php b/tests/src/Functional/GenericTest.php
similarity index 85%
rename from tests/src/Functional/GenericPersistentVisitorParametersTest.php
rename to tests/src/Functional/GenericTest.php
index 2db0b0ba9a87513b9593b1a70dc6eab56c670ee9..0d1da47585a939ca2f9efd15dfb328ff74e32c4b 100644
--- a/tests/src/Functional/GenericPersistentVisitorParametersTest.php
+++ b/tests/src/Functional/GenericTest.php
@@ -9,7 +9,7 @@ use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
  *
  * @group persistent_visitor_parameters
  */
-class GenericPersistentVisitorParametersTest extends GenericModuleTestBase {
+class GenericTest extends GenericModuleTestBase {
 
   /**
    * {@inheritDoc}
diff --git a/tests/src/Functional/PersistorCookieFunctionalTest.php b/tests/src/Functional/PersistorCookieFunctionalTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..31fa1811dd422a67b7f6c6f9d397e66e18b6ad62
--- /dev/null
+++ b/tests/src/Functional/PersistorCookieFunctionalTest.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Drupal\Tests\persistent_visitor_parameters\Functional;
+
+/**
+ * This class provides methods for testing persistent_visitor_parameters.
+ *
+ * @group persistent_visitor_parameters
+ */
+class PersistorCookieFunctionalTest extends PersistorTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    $this->config('persistent_visitor_parameters.settings')
+      ->set('persistor', 'persistor_cookie')
+      ->save();
+  }
+
+  // Other methods are inherited from PersistorTestBase!
+}
diff --git a/tests/src/Functional/PersistorNullFunctionalTest.php b/tests/src/Functional/PersistorNullFunctionalTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..009bab7619ab0a640bd1c5102788a76cfb8783a4
--- /dev/null
+++ b/tests/src/Functional/PersistorNullFunctionalTest.php
@@ -0,0 +1,123 @@
+<?php
+
+namespace Drupal\Tests\persistent_visitor_parameters\Functional;
+
+/**
+ * This class provides methods for testing persistent_visitor_parameters.
+ *
+ * @group persistent_visitor_parameters
+ */
+class PersistorNullFunctionalTest extends PersistorTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    $this->config('persistent_visitor_parameters.settings')
+      ->set('persistor', 'persistor_null')
+      ->save();
+  }
+
+  /**
+   * Tests persisting as authenticated user with first_touch method.
+   */
+  public function testPersistAuthenticatedFirstTouch() {
+    // We don't support tracking for logged in users yet:
+    $this->drupalGet('<front>', [
+      'query' => $this->getGetParameters1(),
+    ]);
+
+    // @improve: We currently test against the debug page
+    // because the test runner has a different session than the
+    // actually tested user.
+    // So we can't simply run
+    // $this->persistenceManager->loadPersistedParameters();
+    // in the tests, as it would always return NULL.
+    // Maybe it's possible to instead use a mock object that is then filled
+    // by the test or whatever, but after investing several hours looking
+    // for solutions and documentation we gave up...
+    // This works good enough for now. Please teach us! ;)
+    $this->drupalGet('/admin/config/people/persistent-visitor-parameters/debug');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains('Persistent Visitor Parameters Debug');
+    $this->assertSession()->pageTextNotContains('Array');
+  }
+
+  /**
+   * Tests persisting as anonymous user with first_touch method.
+   */
+  public function testPersistAnonymousFirstTouch() {
+    // Make a first call:
+    $this->drupalGet('<front>', [
+      'query' => $this->getGetParameters1(),
+    ]);
+    // For the NULL persistor we expect empty results:
+    $this->drupalGet('/admin/config/people/persistent-visitor-parameters/debug');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains('Persistent Visitor Parameters Debug');
+    $this->assertSession()->pageTextNotContains('Array');
+
+    // Make a second call:
+    $this->drupalGet('<front>', [
+      'query' => $this->getGetParameters2(),
+    ]);
+    $this->drupalGet('/admin/config/people/persistent-visitor-parameters/debug');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains('Persistent Visitor Parameters Debug');
+    $this->assertSession()->pageTextNotContains('Array');
+  }
+
+  /**
+   * Tests persisting as authenticated user with last_touch method.
+   */
+  public function testPersistAuthenticatedLastTouch() {
+    // We don't support tracking for logged in users yet:
+    $this->drupalGet('<front>', [
+      'query' => $this->getGetParameters1(),
+    ]);
+
+    // @improve: We currently test against the debug page
+    // because the test runner has a different session than the
+    // actually tested user.
+    // So we can't simply run
+    // $this->persistenceManager->loadPersistedParameters();
+    // in the tests, as it would always return NULL.
+    // Maybe it's possible to instead use a mock object that is then filled
+    // by the test or whatever, but after investing several hours looking
+    // for solutions and documentation we gave up...
+    // This works good enough for now. Please teach us! ;)
+    $this->drupalGet('/admin/config/people/persistent-visitor-parameters/debug');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains('Persistent Visitor Parameters Debug');
+    $this->assertSession()->pageTextNotContains('Array');
+  }
+
+  /**
+   * Tests persisting as anonymous user with last_touch method.
+   */
+  protected function testPersistAnonymousLastTouch() {
+    // Make a first call:
+    $this->drupalGet('<front>', [
+      'query' => $this->getGetParameters1(),
+    ]);
+    // For the NULL persistor we expect empty results:
+    $this->drupalGet('/admin/config/people/persistent-visitor-parameters/debug');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains('Persistent Visitor Parameters Debug');
+    $this->assertSession()->pageTextNotContains('Array');
+
+    // Make a second call:
+    $this->drupalGet('<front>', [
+      'query' => $this->getGetParameters2(),
+    ]);
+    // For the NULL persistor we expect empty results:
+    $this->drupalGet('/admin/config/people/persistent-visitor-parameters/debug');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains('Persistent Visitor Parameters Debug');
+    $this->assertSession()->pageTextNotContains('Array');
+  }
+
+  // Other methods are inherited from PersistorTestBase!
+}
diff --git a/tests/src/Functional/PersistorPrivateTempStoreFunctionalTest.php b/tests/src/Functional/PersistorPrivateTempStoreFunctionalTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..0381c1efb4cdebb4f7324c2ea979704b66c17679
--- /dev/null
+++ b/tests/src/Functional/PersistorPrivateTempStoreFunctionalTest.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Drupal\Tests\persistent_visitor_parameters\Functional;
+
+/**
+ * This class provides methods for testing persistent_visitor_parameters.
+ *
+ * @group persistent_visitor_parameters
+ */
+class PersistorPrivateTempStoreFunctionalTest extends PersistorTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    $this->config('persistent_visitor_parameters.settings')
+      ->set('persistor', 'persistor_private_temp_store')
+      ->save();
+  }
+
+  // Other methods are inherited from PersistorTestBase!
+}
diff --git a/tests/src/Functional/PersistorSessionFunctionalTest.php b/tests/src/Functional/PersistorSessionFunctionalTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..8584cc0ea9ae2962d7d6ae749a0e88456573615e
--- /dev/null
+++ b/tests/src/Functional/PersistorSessionFunctionalTest.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Drupal\Tests\persistent_visitor_parameters\Functional;
+
+/**
+ * This class provides methods for testing persistent_visitor_parameters.
+ *
+ * @group persistent_visitor_parameters
+ */
+class PersistorSessionFunctionalTest extends PersistorTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    $this->config('persistent_visitor_parameters.settings')
+      ->set('persistor', 'persistor_session')
+      ->save();
+  }
+
+  // Other methods are inherited from PersistorTestBase!
+}
diff --git a/tests/src/Functional/PersistorTestBase.php b/tests/src/Functional/PersistorTestBase.php
new file mode 100644
index 0000000000000000000000000000000000000000..4e5b619de637c5c8e50252147b300ee1d4d32de1
--- /dev/null
+++ b/tests/src/Functional/PersistorTestBase.php
@@ -0,0 +1,291 @@
+<?php
+
+namespace Drupal\Tests\persistent_visitor_parameters\Functional;
+
+use Drupal\persistent_visitor_parameters\ParameterManager;
+use Drupal\persistent_visitor_parameters\PersistenceManager;
+use Drupal\Tests\BrowserTestBase;
+use Drupal\user\Entity\Role;
+use Drupal\user\RoleInterface;
+
+/**
+ * This class provides methods for testing persistent_visitor_parameters.
+ *
+ * @group persistent_visitor_parameters
+ */
+abstract class PersistorTestBase extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'test_page_test',
+    'persistent_visitor_parameters',
+  ];
+
+  /**
+   * A user with authenticated permissions.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $user;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * The persistence manager service.
+   *
+   * @var \Drupal\persistent_visitor_parameters\PersistenceManager
+   */
+  protected ?PersistenceManager $persistenceManager;
+
+  /**
+   * The parameter manager service.
+   *
+   * @var \Drupal\persistent_visitor_parameters\ParameterManager|null
+   */
+  protected ?ParameterManager $parameterManager;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    $this->config('system.site')->set('page.front', '/test-page')->save();
+    // Give access persistent_visitor_parameters debug permission
+    // so that we can access the debug page.
+    // This is needed, because we don't know how to easily get the
+    // users session / cookie / ... values as the test runner is
+    // in a different process and we can't simply call
+    // $this->persistenceManager->loadPersistedParameters();
+    $this->grantPermissions(Role::load(RoleInterface::ANONYMOUS_ID), ['access persistent_visitor_parameters debug']);
+    $this->persistenceManager = \Drupal::service('persistent_visitor_parameters.persistence_manager');
+
+    // Set the configuration programmatically, so we don't have to make any
+    // requests to the settings form:
+    $this->config('persistent_visitor_parameters.settings')
+      ->set('get_parameters', ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'])
+      ->set('server_parameters', ['HTTP_REFERER'])
+      ->set('mode', 'last_touch')
+      ->set('respect_dnt', FALSE)
+      ->save();
+  }
+
+  /**
+   * Helper method to return example UTM get parameters.
+   */
+  protected function getGetParameters1(): array {
+    return [
+      'utm_source' => 'foo',
+      'utm_medium' => 'bar',
+      'utm_campaign' => 'baz',
+      'utm_term' => 'qux',
+      'utm_content' => 'tak',
+    ];
+  }
+
+  /**
+   * Helper method to return different example UTM get parameters.
+   */
+  protected function getGetParameters2(): array {
+    return [
+      'utm_source' => 'foo1',
+      'utm_medium' => 'bar1',
+      'utm_campaign' => 'baz1',
+      'utm_term' => 'qux1',
+      'utm_content' => 'tak1',
+    ];
+  }
+
+  /**
+   * Tests persisting as authenticated user with first_touch method.
+   */
+  public function testPersistAuthenticatedFirstTouch() {
+    // We don't support tracking for logged in users yet:
+    $this->drupalGet('<front>', [
+      'query' => $this->getGetParameters1(),
+    ]);
+
+    // @improve: We currently test against the debug page
+    // because the test runner has a different session than the
+    // actually tested user.
+    // So we can't simply run
+    // $this->persistenceManager->loadPersistedParameters();
+    // in the tests, as it would always return NULL.
+    // Maybe it's possible to instead use a mock object that is then filled
+    // by the test or whatever, but after investing several hours looking
+    // for solutions and documentation we gave up...
+    // This works good enough for now. Please teach us! ;)
+    $this->drupalGet('/admin/config/people/persistent-visitor-parameters/debug');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains('Persistent Visitor Parameters Debug');
+    $this->assertSession()->pageTextContains('Array');
+    $this->assertSession()->pageTextContains('[utm_source] => foo');
+    $this->assertSession()->pageTextContains('[utm_medium] => bar');
+    $this->assertSession()->pageTextContains('[utm_campaign] => baz');
+    $this->assertSession()->pageTextContains('[utm_term] => qux');
+    $this->assertSession()->pageTextContains('[utm_content] => tak');
+    $this->assertSession()->pageTextContains('[HTTP_REFERER]');
+
+    // @todo Add, once we have authenticated users support:
+    // Make a second call:
+    // $this->drupalGet('<front>', [
+    // 'query' => $this->getGetParameters2(),
+    // ]);
+  }
+
+  /**
+   * Tests persisting as anonymous user with first_touch method.
+   */
+  public function testPersistAnonymousFirstTouch() {
+    // Make a first call:
+    $this->drupalGet('<front>', [
+      'query' => $this->getGetParameters1(),
+    ]);
+    // @improve: We currently test against the debug page
+    // because the test runner has a different session than the
+    // actually tested user.
+    // So we can't simply run
+    // $this->persistenceManager->loadPersistedParameters();
+    // in the tests, as it would always return NULL.
+    // Maybe it's possible to instead use a mock object that is then filled
+    // by the test or whatever, but after investing several hours looking
+    // for solutions and documentation we gave up...
+    // This works good enough for now. Please teach us! ;)
+    $this->drupalGet('/admin/config/people/persistent-visitor-parameters/debug');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains('Persistent Visitor Parameters Debug');
+    $this->assertSession()->pageTextContains('Array');
+    $this->assertSession()->pageTextContains('[utm_source] => foo');
+    $this->assertSession()->pageTextContains('[utm_medium] => bar');
+    $this->assertSession()->pageTextContains('[utm_campaign] => baz');
+    $this->assertSession()->pageTextContains('[utm_term] => qux');
+    $this->assertSession()->pageTextContains('[utm_content] => tak');
+    $this->assertSession()->pageTextContains('[HTTP_REFERER]');
+
+    // Make a second call:
+    $this->drupalGet('<front>', [
+      'query' => $this->getGetParameters2(),
+    ]);
+    // Results should be same to first call:
+    // @improve: We currently test against the debug page
+    // because the test runner has a different session than the
+    // actually tested user.
+    // So we can't simply run
+    // $this->persistenceManager->loadPersistedParameters();
+    // in the tests, as it would always return NULL.
+    // Maybe it's possible to instead use a mock object that is then filled
+    // by the test or whatever, but after investing several hours looking
+    // for solutions and documentation we gave up...
+    // This works good enough for now. Please teach us! ;)
+    $this->drupalGet('/admin/config/people/persistent-visitor-parameters/debug');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains('Persistent Visitor Parameters Debug');
+    $this->assertSession()->pageTextContains('Array');
+    $this->assertSession()->pageTextContains('[utm_source] => foo');
+    $this->assertSession()->pageTextContains('[utm_medium] => bar');
+    $this->assertSession()->pageTextContains('[utm_campaign] => baz');
+    $this->assertSession()->pageTextContains('[utm_term] => qux');
+    $this->assertSession()->pageTextContains('[utm_content] => tak');
+    $this->assertSession()->pageTextContains('[HTTP_REFERER]');
+  }
+
+  /**
+   * Tests persisting as authenticated user with last_touch method.
+   */
+  protected function testPersistAuthenticatedLastTouch() {
+    // We don't support tracking for logged in users yet:
+    $this->drupalGet('<front>', [
+      'query' => $this->getGetParameters1(),
+    ]);
+    // @improve: We currently test against the debug page
+    // because the test runner has a different session than the
+    // actually tested user.
+    // So we can't simply run
+    // $this->persistenceManager->loadPersistedParameters();
+    // in the tests, as it would always return NULL.
+    // Maybe it's possible to instead use a mock object that is then filled
+    // by the test or whatever, but after investing several hours looking
+    // for solutions and documentation we gave up...
+    // This works good enough for now. Please teach us! ;)
+    $this->drupalGet('/admin/config/people/persistent-visitor-parameters/debug');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains('Persistent Visitor Parameters Debug');
+    $this->assertSession()->pageTextContains('Array');
+    $this->assertSession()->pageTextContains('[utm_source] => foo');
+    $this->assertSession()->pageTextContains('[utm_medium] => bar');
+    $this->assertSession()->pageTextContains('[utm_campaign] => baz');
+    $this->assertSession()->pageTextContains('[utm_term] => qux');
+    $this->assertSession()->pageTextContains('[utm_content] => tak');
+    $this->assertSession()->pageTextContains('[HTTP_REFERER]');
+
+    // @todo Add, once we have authenticated users support:
+    // // Make a second call:
+    // $this->drupalGet('<front>', [
+    // 'query' => $this->getGetParameters2(),
+    // ]);
+  }
+
+  /**
+   * Tests persisting as anonymous user with last_touch method.
+   */
+  protected function testPersistAnonymousLastTouch() {
+    // Make a first call:
+    $this->drupalGet('<front>', [
+      'query' => $this->getGetParameters1(),
+    ]);
+    // Expect the parameters:
+    // @improve: We currently test against the debug page
+    // because the test runner has a different session than the
+    // actually tested user.
+    // So we can't simply run
+    // $this->persistenceManager->loadPersistedParameters();
+    // in the tests, as it would always return NULL.
+    // Maybe it's possible to instead use a mock object that is then filled
+    // by the test or whatever, but after investing several hours looking
+    // for solutions and documentation we gave up...
+    // This works good enough for now. Please teach us! ;)
+    $this->drupalGet('/admin/config/people/persistent-visitor-parameters/debug');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains('Persistent Visitor Parameters Debug');
+    $this->assertSession()->pageTextContains('Array');
+    $this->assertSession()->pageTextContains('[utm_source] => foo');
+    $this->assertSession()->pageTextContains('[utm_medium] => bar');
+    $this->assertSession()->pageTextContains('[utm_campaign] => baz');
+    $this->assertSession()->pageTextContains('[utm_term] => qux');
+    $this->assertSession()->pageTextContains('[utm_content] => tak');
+    $this->assertSession()->pageTextContains('[HTTP_REFERER]');
+
+    // Make a second call:
+    $this->drupalGet('<front>', [
+      'query' => $this->getGetParameters2(),
+    ]);
+    // Results should be different from first call:
+    // @improve: We currently test against the debug page
+    // because the test runner has a different session than the
+    // actually tested user.
+    // So we can't simply run
+    // $this->persistenceManager->loadPersistedParameters();
+    // in the tests, as it would always return NULL.
+    // Maybe it's possible to instead use a mock object that is then filled
+    // by the test or whatever, but after investing several hours looking
+    // for solutions and documentation we gave up...
+    // This works good enough for now. Please teach us! ;)
+    $this->drupalGet('/admin/config/people/persistent-visitor-parameters/debug');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains('Persistent Visitor Parameters Debug');
+    $this->assertSession()->pageTextContains('Array');
+    $this->assertSession()->pageTextContains('[utm_source] => foo1');
+    $this->assertSession()->pageTextContains('[utm_medium] => bar1');
+    $this->assertSession()->pageTextContains('[utm_campaign] => baz1');
+    $this->assertSession()->pageTextContains('[utm_term] => qux1');
+    $this->assertSession()->pageTextContains('[utm_content] => tak1');
+    $this->assertSession()->pageTextContains('[HTTP_REFERER]');
+
+  }
+
+}