diff --git a/composer.json b/composer.json
index 721dc1968f132368a2f7a56f2f8c96825b28a217..b85a7141e9a60e54004e47f75272bcba7032a79f 100644
--- a/composer.json
+++ b/composer.json
@@ -298,7 +298,8 @@
 			"TYPO3Tests\\TestDataMapper\\": "typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/test_data_mapper/Classes/",
 			"TYPO3Tests\\TestFluidTemplate\\": "typo3/sysext/frontend/Tests/Functional/Fixtures/Extensions/test_fluid_template/Classes/",
 			"TYPO3Tests\\TestLogger\\": "typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_logger/Classes/",
-			"TYPO3Tests\\TestTyposcriptAstFunctionEvent\\": "typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_ast_function_event/Classes/"
+			"TYPO3Tests\\TestTyposcriptAstFunctionEvent\\": "typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_ast_function_event/Classes/",
+			"TYPO3Tests\\TestTyposcriptPagetsconfigfactory\\": "typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Classes/"
 		},
 		"classmap": [
 			"typo3/sysext/core/Tests/Unit/Core/Fixtures/test_extension/",
diff --git a/typo3/sysext/backend/Classes/Form/Element/BackendLayoutWizardElement.php b/typo3/sysext/backend/Classes/Form/Element/BackendLayoutWizardElement.php
index e6459ed57c76217c33cf124f09aaa7f8feb2ad0a..2075aa8bc3707e0fea8aa174be4c7a98142c7d1a 100644
--- a/typo3/sysext/backend/Classes/Form/Element/BackendLayoutWizardElement.php
+++ b/typo3/sysext/backend/Classes/Form/Element/BackendLayoutWizardElement.php
@@ -19,7 +19,6 @@ use TYPO3\CMS\Core\EventDispatcher\NoopEventDispatcher;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction;
 use TYPO3\CMS\Core\TypoScript\AST\AstBuilder;
-use TYPO3\CMS\Core\TypoScript\Tokenizer\LossyTokenizer;
 use TYPO3\CMS\Core\TypoScript\TypoScriptStringFactory;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -198,11 +197,7 @@ class BackendLayoutWizardElement extends AbstractFormElement
         if (!empty($this->data['parameterArray']['itemFormElValue'])) {
             // Parse the TypoScript a-like syntax in case we already have a config (e.g. database value or default from TCA)
             $typoScriptStringFactory = GeneralUtility::makeInstance(TypoScriptStringFactory::class);
-            $typoScriptTree = $typoScriptStringFactory->parseFromString(
-                $this->data['parameterArray']['itemFormElValue'],
-                new LossyTokenizer(),
-                new AstBuilder(new NoopEventDispatcher())
-            );
+            $typoScriptTree = $typoScriptStringFactory->parseFromString($this->data['parameterArray']['itemFormElValue'], new AstBuilder(new NoopEventDispatcher()));
             $typoScriptArray = $typoScriptTree->toArray();
             if (is_array($typoScriptArray['backend_layout.'] ?? false)) {
                 // Only evaluate, in case the "backend_layout." array exists on root level
diff --git a/typo3/sysext/backend/Classes/Utility/BackendUtility.php b/typo3/sysext/backend/Classes/Utility/BackendUtility.php
index 2a65669b4ba99ea0961dba6a077748c5c2c89c74..8b8b4ecd328a6940b729cf858b3efda62f951475 100644
--- a/typo3/sysext/backend/Classes/Utility/BackendUtility.php
+++ b/typo3/sysext/backend/Classes/Utility/BackendUtility.php
@@ -20,7 +20,7 @@ use Doctrine\DBAL\Types\Type;
 use Psr\Http\Message\ServerRequestInterface;
 use Psr\Log\LoggerInterface;
 use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
-use TYPO3\CMS\Backend\Configuration\TypoScript\ConditionMatching\ConditionMatcher;
+use TYPO3\CMS\Backend\Configuration\TypoScript\ConditionMatching\ConditionMatcher as BackendConditionMatcher;
 use TYPO3\CMS\Backend\Domain\Model\Element\ImmediateActionElement;
 use TYPO3\CMS\Backend\Module\ModuleProvider;
 use TYPO3\CMS\Backend\Routing\Route;
@@ -28,7 +28,6 @@ use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
-use TYPO3\CMS\Core\Configuration\PageTsConfig;
 use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Context\DateTimeAspect;
 use TYPO3\CMS\Core\Core\Environment;
@@ -55,8 +54,11 @@ use TYPO3\CMS\Core\Resource\ResourceFactory;
 use TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException;
 use TYPO3\CMS\Core\Routing\RouterInterface;
 use TYPO3\CMS\Core\Routing\UnableToLinkToPageException;
+use TYPO3\CMS\Core\Site\Entity\NullSite;
 use TYPO3\CMS\Core\Site\SiteFinder;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
+use TYPO3\CMS\Core\TypoScript\PageTsConfig;
+use TYPO3\CMS\Core\TypoScript\PageTsConfigFactory;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -687,27 +689,40 @@ class BackendUtility
      * TypoScript related
      *
      *******************************************/
+
     /**
-     * Returns the Page TSconfig for page with id, $id
-     *
-     * @param int $id Page uid for which to create Page TSconfig
-     * @return array Page TSconfig
+     * Returns the PageTsConfig for page with uid $pageUid
      */
-    public static function getPagesTSconfig($id)
+    public static function getPagesTSconfig($pageUid): array
     {
-        $id = (int)$id;
-        $rootLine = self::BEgetRootLine($id, '', true);
+        $runtimeCache = static::getRuntimeCache();
+        $pageTsConfig = $runtimeCache->get('pageTsConfig-' . $pageUid);
+        if ($pageTsConfig instanceof PageTsConfig) {
+            return $pageTsConfig->getPageTsConfigArray();
+        }
+
+        $pageUid = (int)$pageUid;
+        $rootLine = self::BEgetRootLine($pageUid, '', true);
         // Order correctly
         ksort($rootLine);
 
         try {
-            $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($id);
-        } catch (SiteNotFoundException $exception) {
-            $site = null;
-        }
-        $matcher = GeneralUtility::makeInstance(ConditionMatcher::class, GeneralUtility::makeInstance(Context::class), $id, $rootLine);
-        $tsConfig = GeneralUtility::makeInstance(PageTsConfig::class);
-        return $tsConfig->getWithUserOverride($id, $rootLine, $site, $matcher, static::getBackendUserAuthentication());
+            $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($pageUid);
+        } catch (SiteNotFoundException) {
+            $site = new NullSite();
+        }
+
+        $conditionMatcher = GeneralUtility::makeInstance(BackendConditionMatcher::class, GeneralUtility::makeInstance(Context::class), $pageUid, $rootLine);
+        $pageTsConfigFactory = GeneralUtility::makeInstance(PageTsConfigFactory::class);
+        $pageTsConfig = $pageTsConfigFactory->create(
+            $rootLine,
+            $site,
+            $conditionMatcher,
+            static::getBackendUserAuthentication()?->getUserTsConfig()
+        );
+
+        $runtimeCache->set('pageTsConfig-' . $pageUid, $pageTsConfig);
+        return $pageTsConfig->getPageTsConfigArray();
     }
 
     /*******************************************
diff --git a/typo3/sysext/backend/Classes/View/BackendLayoutView.php b/typo3/sysext/backend/Classes/View/BackendLayoutView.php
index 1502fe29fea251f66a1f2e0402b5a92b6fc0ca5b..43cf692d903dbebbe20106289a3fd392c08cced2 100644
--- a/typo3/sysext/backend/Classes/View/BackendLayoutView.php
+++ b/typo3/sysext/backend/Classes/View/BackendLayoutView.php
@@ -21,12 +21,10 @@ use TYPO3\CMS\Backend\View\BackendLayout\BackendLayout;
 use TYPO3\CMS\Backend\View\BackendLayout\DataProviderCollection;
 use TYPO3\CMS\Backend\View\BackendLayout\DataProviderContext;
 use TYPO3\CMS\Backend\View\BackendLayout\DefaultDataProvider;
-use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\SingletonInterface;
-use TYPO3\CMS\Core\TypoScript\Tokenizer\TokenizerInterface;
 use TYPO3\CMS\Core\TypoScript\TypoScriptStringFactory;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -47,8 +45,6 @@ class BackendLayoutView implements SingletonInterface
         private readonly DataProviderCollection $dataProviderCollection,
         private readonly TypoScriptStringFactory $typoScriptStringFactory,
         private readonly BackendConditionMatcher $conditionMatcher,
-        private readonly TokenizerInterface $tokenizer,
-        private readonly PhpFrontend $typoScriptCache,
     ) {
         $this->dataProviderCollection->add('default', DefaultDataProvider::class);
         if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['BackendLayoutDataProvider'])) {
@@ -346,9 +342,7 @@ class BackendLayoutView implements SingletonInterface
         $typoScriptTree = $this->typoScriptStringFactory->parseFromStringWithIncludesAndConditions(
             'backend-layout',
             $backendLayout->getConfiguration(),
-            $this->tokenizer,
-            $this->conditionMatcher,
-            $this->typoScriptCache
+            $this->conditionMatcher
         );
 
         $backendLayoutData = [];
diff --git a/typo3/sysext/backend/Classes/View/BackendViewFactory.php b/typo3/sysext/backend/Classes/View/BackendViewFactory.php
index bb0fe3e75697839f11cb544fb6dd767db61ae204..e58f968264b4f47d5df105814c9bce13bb16089e 100644
--- a/typo3/sysext/backend/Classes/View/BackendViewFactory.php
+++ b/typo3/sysext/backend/Classes/View/BackendViewFactory.php
@@ -22,6 +22,7 @@ use TYPO3\CMS\Backend\Routing\Route;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Package\PackageManager;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Core\View\FluidViewAdapter;
 use TYPO3\CMS\Core\View\ViewInterface as CoreViewInterface;
 use TYPO3\CMS\Fluid\Core\Rendering\RenderingContextFactory;
@@ -69,8 +70,15 @@ final class BackendViewFactory
 
         // @todo: This assumes the pageId is *always* given as 'id' in request.
         // @todo: It would be cool if a middleware adds final pageTS - already overlayed by userTS - as attribute to request, to use it here.
+        $pageTs = [];
         $pageId = $request->getParsedBody()['id'] ?? $request->getQueryParams()['id'] ?? 0;
-        $pageTs = BackendUtility::getPagesTSconfig($pageId);
+        if (MathUtility::canBeInterpretedAsInteger($pageId)) {
+            // Some BE controllers misuse the 'id' argument for something else than the page-uid (especially filelist module).
+            // We check if 'id' is an integer here to skip pageTsConfig calculation if that is the case.
+            // @todo: Mid-term, misuses should vanish, making 'id' a Backend convention. Affected is
+            //        at least ext:filelist, plus record linking modals that use 'pid'.
+            $pageTs = BackendUtility::getPagesTSconfig((int)$pageId);
+        }
 
         $templatePaths = [
             'templateRootPaths' => [],
diff --git a/typo3/sysext/backend/Configuration/Services.yaml b/typo3/sysext/backend/Configuration/Services.yaml
index c0d28a11d0412db562921c82880e2989a3486534..1d00f5d171566980101fa706f62c160ecffa7b8e 100644
--- a/typo3/sysext/backend/Configuration/Services.yaml
+++ b/typo3/sysext/backend/Configuration/Services.yaml
@@ -70,10 +70,6 @@ services:
   TYPO3\CMS\Backend\View\AuthenticationStyleInformation:
     public: true
 
-  TYPO3\CMS\Backend\View\BackendLayoutView:
-    arguments:
-      $typoScriptCache: '@cache.typoscript'
-
   TYPO3\CMS\Backend\Search\LiveSearch\SearchProviderRegistry:
     arguments:
       - !tagged_iterator livesearch.provider
diff --git a/typo3/sysext/core/Classes/Authentication/BackendUserAuthentication.php b/typo3/sysext/core/Classes/Authentication/BackendUserAuthentication.php
index 87dd392c3d2d81a8ae42a24a236e9e3b75cd17eb..497a60a8ce04fc2811e95b0b7425bf3946f6b9b4 100644
--- a/typo3/sysext/core/Classes/Authentication/BackendUserAuthentication.php
+++ b/typo3/sysext/core/Classes/Authentication/BackendUserAuthentication.php
@@ -15,7 +15,6 @@
 
 namespace TYPO3\CMS\Core\Authentication;
 
-use TYPO3\CMS\Backend\Configuration\TypoScript\ConditionMatching\ConditionMatcher;
 use TYPO3\CMS\Backend\Module\ModuleProvider;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Cache\CacheManager;
@@ -46,7 +45,8 @@ use TYPO3\CMS\Core\Type\Bitmask\BackendGroupMountOption;
 use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\Type\Exception\InvalidEnumerationValueException;
-use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
+use TYPO3\CMS\Core\TypoScript\UserTsConfig;
+use TYPO3\CMS\Core\TypoScript\UserTsConfigFactory;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\StringUtility;
@@ -111,15 +111,13 @@ class BackendUserAuthentication extends AbstractUserAuthentication
      */
     public $workspaceRec = [];
 
-    /**
-     * @var array Parsed user TSconfig
-     */
-    protected $userTS = [];
+    protected ?UserTsConfig $userTsConfig = null;
 
     /**
-     * @var bool True if the user TSconfig was parsed and needs to be cached.
+     * True if the user TSconfig was parsed and needs to be cached.
+     * @todo: Should vanish, see todo below.
      */
-    protected $userTSUpdated = false;
+    protected bool $userTSUpdated = false;
 
     /**
      * Contains last error message
@@ -968,9 +966,19 @@ class BackendUserAuthentication extends AbstractUserAuthentication
      *
      * @return array Parsed and merged user TSconfig array
      */
-    public function getTSConfig()
+    public function getTSConfig(): array
+    {
+        return $this->getUserTsConfig()?->getUserTsConfigArray() ?? [];
+    }
+
+    /**
+     * Return the full UserTsConfig object instead of just the array as in getTSConfig()
+     *
+     * @internal for now until API stabilized
+     */
+    public function getUserTsConfig(): ?UserTsConfig
     {
-        return $this->userTS;
+        return $this->userTsConfig;
     }
 
     /**
@@ -1058,7 +1066,6 @@ class BackendUserAuthentication extends AbstractUserAuthentication
      * Generally this is required initialization of a backend user.
      *
      * @internal
-     * @see \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser
      */
     public function fetchGroupData()
     {
@@ -1133,8 +1140,8 @@ class BackendUserAuthentication extends AbstractUserAuthentication
                 }
             }
 
-            // Populating the $this->userGroupsUID -array with the groups in the order in which they were LAST included.!!
-            // Finally this is the list of group_uid's in the order they are parsed (including subgroups!)
+            // Populating the $this->userGroupsUID -array with the groups in the order in which they were LAST included.
+            // Finally, this is the list of group_uid's in the order they are parsed (including subgroups)
             // and without duplicates (duplicates are presented with their last entrance in the list,
             // which thus reflects the order of the TypoScript in TSconfig)
             $this->userGroupsUID = array_reverse(array_unique(array_reverse($this->userGroupsUID)));
@@ -1214,43 +1221,28 @@ class BackendUserAuthentication extends AbstractUserAuthentication
     }
 
     /**
-     * This method parses the UserTSconfig from the current user and all their groups.
-     * If the contents are the same, parsing is skipped. No matching is applied here currently.
+     * Parse userTsConfig from current user and its groups and set it as $this->userTS.
      */
     protected function prepareUserTsConfig(): void
     {
-        $collectedUserTSconfig = [
-            'default' => $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultUserTSconfig'],
-        ];
-        // Default TSconfig for admin-users
-        if ($this->isAdmin()) {
-            $collectedUserTSconfig[] = 'admPanel.enable.all = 1';
-        }
-        // Setting defaults for sys_note author / email
-        $collectedUserTSconfig[] = '
-TCAdefaults.sys_note.author = ' . $this->user['realName'] . '
-TCAdefaults.sys_note.email = ' . $this->user['email'];
-
-        // Loop through all groups and add their 'TSconfig' fields
-        foreach ($this->userGroupsUID as $groupId) {
-            $collectedUserTSconfig['group_' . $groupId] = $this->userGroups[$groupId]['TSconfig'] ?? '';
-        }
-
-        $collectedUserTSconfig[] = $this->user['TSconfig'];
-        // Check external files
-        $collectedUserTSconfig = TypoScriptParser::checkIncludeLines_array($collectedUserTSconfig);
-        // Imploding with "[global]" will make sure that non-ended confinements with braces are ignored.
-        $userTS_text = implode("\n[GLOBAL]\n", $collectedUserTSconfig);
-        // Parsing the user TSconfig (or getting from cache)
-        $hash = md5('userTS:' . $userTS_text);
-        $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('hash');
-        if (!($this->userTS = $cache->get($hash))) {
-            $parseObj = GeneralUtility::makeInstance(TypoScriptParser::class);
-            $conditionMatcher = GeneralUtility::makeInstance(ConditionMatcher::class);
-            $parseObj->parse($userTS_text, $conditionMatcher);
-            $this->userTS = $parseObj->setup;
-            $cache->set($hash, $this->userTS, ['UserTSconfig'], 0);
-            // Ensure to update UC later
+        $tsConfigFactory = GeneralUtility::makeInstance(UserTsConfigFactory::class);
+        $this->userTsConfig = $tsConfigFactory->create($this);
+        if (!empty($this->getUserTsConfig()->getUserTsConfigArray()['setup.']['override.'])) {
+            // @todo: This logic is ugly. userTsConfig "setup.override." is used to force options
+            //        in the user settings module, along with "setup.fields." and "setup.default.".
+            //        See the docs about this.
+            //        The fun part is, this is merged into user UC. As such, whenever these setup
+            //        options are used, user UC has to be updated. The toggle below triggers this
+            //        and initiates an update query of this users UC.
+            //        Before v12, this was only triggered if userTsConfig could not be fetched from
+            //        cache, but this was flawed, too: When two users had the same userTsConfig, UC
+            //        of one user would be updated, but not UC of the other user if caches were not
+            //        cleared in between their two calls.
+            //        This toggle and UC overriding should vanish altogether: It would be better if
+            //        userTsConfig no longer overlays UC, instead the settings / setup module
+            //        controller should look at userTsConfig "setup." on the fly when rendering, and
+            //        consumers that access user setup / settings values should get values overloaded
+            //        on the fly as well using some helper or a late init logic or similar.
             $this->userTSUpdated = true;
         }
     }
diff --git a/typo3/sysext/core/Classes/Configuration/Event/ModifyLoadedPageTsConfigEvent.php b/typo3/sysext/core/Classes/Configuration/Event/ModifyLoadedPageTsConfigEvent.php
index fa7eafeb46cd90771d829e17d7ac6cc1fd2b8b12..26f4c0926181f7e9dd83cbb7cfa61d76b0c55437 100644
--- a/typo3/sysext/core/Classes/Configuration/Event/ModifyLoadedPageTsConfigEvent.php
+++ b/typo3/sysext/core/Classes/Configuration/Event/ModifyLoadedPageTsConfigEvent.php
@@ -19,30 +19,11 @@ namespace TYPO3\CMS\Core\Configuration\Event;
 
 /**
  * Extensions can modify pageTSConfig entries that can be overridden or added, based on the root line
+ *
+ * @deprecated since v12, will be removed in v13. Switch to \TYPO3\CMS\Core\TypoScript\IncludeTree\Event\ModifyLoadedPageTsConfigEvent.
+ *             When removing, delete the class, adapt test_typoscript_pagetsconfigfactory test extension, related test and
+ *             set event \TYPO3\CMS\Core\TypoScript\IncludeTree\Event\ModifyLoadedPageTsConfigEvent final.
  */
-final class ModifyLoadedPageTsConfigEvent
+class ModifyLoadedPageTsConfigEvent extends \TYPO3\CMS\Core\TypoScript\IncludeTree\Event\ModifyLoadedPageTsConfigEvent
 {
-    public function __construct(private array $tsConfig, private readonly array $rootLine)
-    {
-    }
-
-    public function getTsConfig(): array
-    {
-        return $this->tsConfig;
-    }
-
-    public function addTsConfig(string $tsConfig): void
-    {
-        $this->tsConfig[] = $tsConfig;
-    }
-
-    public function setTsConfig(array $tsConfig): void
-    {
-        $this->tsConfig = $tsConfig;
-    }
-
-    public function getRootLine(): array
-    {
-        return $this->rootLine;
-    }
 }
diff --git a/typo3/sysext/core/Classes/Configuration/ExtensionConfiguration.php b/typo3/sysext/core/Classes/Configuration/ExtensionConfiguration.php
index 699c08b4bc89c8ca854b38f9f595bb14ee67d1b8..3b37a479df783a7e3d9ebafcf0974be087f76aed 100644
--- a/typo3/sysext/core/Classes/Configuration/ExtensionConfiguration.php
+++ b/typo3/sysext/core/Classes/Configuration/ExtensionConfiguration.php
@@ -22,7 +22,6 @@ use TYPO3\CMS\Core\Configuration\Exception\ExtensionConfigurationPathDoesNotExis
 use TYPO3\CMS\Core\EventDispatcher\NoopEventDispatcher;
 use TYPO3\CMS\Core\Package\PackageManager;
 use TYPO3\CMS\Core\TypoScript\AST\AstBuilder;
-use TYPO3\CMS\Core\TypoScript\Tokenizer\LossyTokenizer;
 use TYPO3\CMS\Core\TypoScript\TypoScriptStringFactory;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -249,7 +248,7 @@ class ExtensionConfiguration
     {
         $rawConfigurationString = $this->getDefaultConfigurationRawString($extensionKey);
         $typoScriptStringFactory = GeneralUtility::makeInstance(TypoScriptStringFactory::class);
-        $typoScriptTree = $typoScriptStringFactory->parseFromString($rawConfigurationString, new LossyTokenizer(), new AstBuilder(new NoopEventDispatcher()));
+        $typoScriptTree = $typoScriptStringFactory->parseFromString($rawConfigurationString, new AstBuilder(new NoopEventDispatcher()));
         return GeneralUtility::removeDotsFromTS($typoScriptTree->toArray());
     }
 
diff --git a/typo3/sysext/core/Classes/Configuration/Loader/PageTsConfigLoader.php b/typo3/sysext/core/Classes/Configuration/Loader/PageTsConfigLoader.php
index d9d236762558cb45c4407e722904808b8534dbd7..dc90d1713e677fc7fb4bdce2c7efc595f70d6540 100644
--- a/typo3/sysext/core/Classes/Configuration/Loader/PageTsConfigLoader.php
+++ b/typo3/sysext/core/Classes/Configuration/Loader/PageTsConfigLoader.php
@@ -34,6 +34,9 @@ use TYPO3\CMS\Core\Utility\PathUtility;
  *
  * Currently, this accumulated information of the pages is NOT cached, as it would need to be tagged with any
  * page, also including external files.
+ *
+ * @deprecated since TYPO3 v12, will be removed with v13. Use PageTsConfigFactory instead.
+ *             When removing, also remove entries in core ServiceProvider, AbstractServiceProvider and Services.yaml.
  */
 class PageTsConfigLoader
 {
@@ -42,6 +45,7 @@ class PageTsConfigLoader
 
     public function __construct(EventDispatcherInterface $eventDispatcher)
     {
+        trigger_error('Class ' . __CLASS__ . ' will be removed with TYPO3 v13.0. Use PageTsConfigFactory instead.', E_USER_DEPRECATED);
         $this->eventDispatcher = $eventDispatcher;
     }
 
diff --git a/typo3/sysext/core/Classes/Configuration/PageTsConfig.php b/typo3/sysext/core/Classes/Configuration/PageTsConfig.php
index 3413e38897b50b6c4ac9113323998570f95fdc87..b60007cacef1878d7dde71574f0ca2368399898e 100644
--- a/typo3/sysext/core/Classes/Configuration/PageTsConfig.php
+++ b/typo3/sysext/core/Classes/Configuration/PageTsConfig.php
@@ -26,6 +26,9 @@ use TYPO3\CMS\Core\Site\Entity\Site;
 
 /**
  * Main entry point for fetching PageTsConfig for frontend and backend.
+ *
+ * @deprecated since TYPO3 v12, will be removed with v13. Use PageTsConfigFactory instead.
+ *             When removing, also remove entries in core Services.yaml and usage in TypoScriptFrontendController.
  */
 class PageTsConfig
 {
@@ -35,6 +38,7 @@ class PageTsConfig
 
     public function __construct(FrontendInterface $cache, PageTsConfigLoader $loader, PageTsConfigParser $parser)
     {
+        trigger_error('Class ' . __CLASS__ . ' will be removed with TYPO3 v13.0. Use PageTsConfigFactory instead.', E_USER_DEPRECATED);
         $this->cache = $cache;
         $this->loader = $loader;
         $this->parser = $parser;
diff --git a/typo3/sysext/core/Classes/Configuration/Parser/PageTsConfigParser.php b/typo3/sysext/core/Classes/Configuration/Parser/PageTsConfigParser.php
index f8d54e8268727a74d3f1b6845195b1241c3e743c..3fa09b8d58c316638ae387922025431ec84af4e2 100644
--- a/typo3/sysext/core/Classes/Configuration/Parser/PageTsConfigParser.php
+++ b/typo3/sysext/core/Classes/Configuration/Parser/PageTsConfigParser.php
@@ -27,6 +27,9 @@ use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
  *
  * This class does parsing of a compiled TSconfig string, and applies matching() based on the
  * Context (FE or BE) in it, allowing to be fully agnostic to the outside world.
+ *
+ * @deprecated since TYPO3 v12, will be removed with v13. Use PageTsConfigFactory instead.
+ *             When removing, also remove entries in core Services.yaml.
  */
 class PageTsConfigParser
 {
@@ -35,6 +38,7 @@ class PageTsConfigParser
 
     public function __construct(TypoScriptParser $typoScriptParser, FrontendInterface $cache)
     {
+        trigger_error('Class ' . __CLASS__ . ' will be removed with TYPO3 v13.0. Use PageTsConfigFactory instead.', E_USER_DEPRECATED);
         $this->typoScriptParser = $typoScriptParser;
         $this->cache = $cache;
     }
diff --git a/typo3/sysext/core/Classes/Package/AbstractServiceProvider.php b/typo3/sysext/core/Classes/Package/AbstractServiceProvider.php
index d192ca447e0c69a469b5fb83a7fccc4d8b6fa97d..ca2effb8b5dee44b65d71eedaf460575986e9c6a 100644
--- a/typo3/sysext/core/Classes/Package/AbstractServiceProvider.php
+++ b/typo3/sysext/core/Classes/Package/AbstractServiceProvider.php
@@ -50,6 +50,7 @@ abstract class AbstractServiceProvider implements ServiceProviderInterface
         return [
             'middlewares' => [ static::class, 'configureMiddlewares' ],
             'backend.routes' => [ static::class, 'configureBackendRoutes' ],
+            // @deprecated since v12, will be removed with v13 together with class PageTsConfigLoader.
             'globalPageTsConfig' => [ static::class, 'configureGlobalPageTsConfig' ],
             'backend.modules' => [ static::class, 'configureBackendModules' ],
             'icons' => [ static::class, 'configureIcons' ],
@@ -110,6 +111,9 @@ abstract class AbstractServiceProvider implements ServiceProviderInterface
         return $routes;
     }
 
+    /**
+     * @deprecated since v12, will be removed with v13 together with class PageTsConfigLoader.
+     */
     public static function configureGlobalPageTsConfig(ContainerInterface $container, ArrayObject $tsConfigFiles, string $path = null): ArrayObject
     {
         $path = $path ?? static::getPackagePath();
diff --git a/typo3/sysext/core/Classes/ServiceProvider.php b/typo3/sysext/core/Classes/ServiceProvider.php
index 86f7cdbfa70a0398507fc3a5caaab0e8ff1393a6..c722683e03c9b439eef39ad31349747b1457ddcb 100644
--- a/typo3/sysext/core/Classes/ServiceProvider.php
+++ b/typo3/sysext/core/Classes/ServiceProvider.php
@@ -30,6 +30,7 @@ use TYPO3\CMS\Core\DependencyInjection\ContainerBuilder;
 use TYPO3\CMS\Core\Imaging\IconRegistry;
 use TYPO3\CMS\Core\Package\AbstractServiceProvider;
 use TYPO3\CMS\Core\Package\PackageManager;
+use TYPO3\CMS\Core\TypoScript\Tokenizer\LossyTokenizer;
 
 /**
  * @internal
@@ -99,6 +100,7 @@ class ServiceProvider extends AbstractServiceProvider
             TypoScript\AST\Traverser\AstTraverser::class => [ static::class, 'getAstTraverser' ],
             TypoScript\AST\CommentAwareAstBuilder::class => [ static::class, 'getCommentAwareAstBuilder' ],
             TypoScript\Tokenizer\LosslessTokenizer::class => [ static::class, 'getLosslessTokenizer'],
+            // @deprecated since v12, will be removed with v13 together with class PageTsConfigLoader.
             'globalPageTsConfig' => [ static::class, 'getGlobalPageTsConfig' ],
             'icons' => [ static::class, 'getIcons' ],
             'middlewares' => [ static::class, 'getMiddlewares' ],
@@ -110,6 +112,7 @@ class ServiceProvider extends AbstractServiceProvider
         return [
             Console\CommandRegistry::class => [ static::class, 'configureCommands' ],
             Imaging\IconRegistry::class => [ static::class, 'configureIconRegistry' ],
+            // @deprecated since v12, will be removed with v13 together with class PageTsConfigLoader.
             Configuration\Loader\PageTsConfigLoader::class => [ static::class, 'configurePageTsConfigLoader' ],
             EventDispatcherInterface::class => [ static::class, 'provideFallbackEventDispatcher' ],
             EventDispatcher\ListenerProvider::class => [ static::class, 'extendEventListenerProvider' ],
@@ -326,11 +329,17 @@ class ServiceProvider extends AbstractServiceProvider
         return self::new($container, Imaging\IconRegistry::class, [$container->get('cache.assets'), $container->get(Package\Cache\PackageDependentCacheIdentifier::class)->withPrefix('BackendIcons')->toString()]);
     }
 
+    /**
+     * @deprecated since v12, will be removed with v13 together with class PageTsConfigLoader.
+     */
     public static function getGlobalPageTsConfig(ContainerInterface $container): ArrayObject
     {
         return new ArrayObject();
     }
 
+    /**
+     * @deprecated since v12, will be removed with v13 together with class PageTsConfigLoader.
+     */
     public static function configurePageTsConfigLoader(ContainerInterface $container, PageTsConfigLoader $configLoader): PageTsConfigLoader
     {
         $cache = $container->get('cache.core');
@@ -480,7 +489,7 @@ class ServiceProvider extends AbstractServiceProvider
 
     public static function getTypoScriptStringFactory(ContainerInterface $container): TypoScript\TypoScriptStringFactory
     {
-        return new TypoScript\TypoScriptStringFactory($container);
+        return new TypoScript\TypoScriptStringFactory($container, new LossyTokenizer());
     }
 
     public static function getTypoScriptService(ContainerInterface $container): TypoScript\TypoScriptService
diff --git a/typo3/sysext/core/Classes/TypoScript/IncludeTree/Event/ModifyLoadedPageTsConfigEvent.php b/typo3/sysext/core/Classes/TypoScript/IncludeTree/Event/ModifyLoadedPageTsConfigEvent.php
new file mode 100644
index 0000000000000000000000000000000000000000..7a47d045e2c5230accc6cc85d8cd4c0ad66b0b9a
--- /dev/null
+++ b/typo3/sysext/core/Classes/TypoScript/IncludeTree/Event/ModifyLoadedPageTsConfigEvent.php
@@ -0,0 +1,50 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Core\TypoScript\IncludeTree\Event;
+
+/**
+ * Extensions can modify pageTsConfig entries that can be overridden or added, based on the root line
+ *
+ * @todo: Set final in v13 when class \TYPO3\CMS\Core\Configuration\Event\ModifyLoadedPageTsConfigEvent is gone.
+ */
+class ModifyLoadedPageTsConfigEvent
+{
+    public function __construct(private array $tsConfig, private readonly array $rootLine)
+    {
+    }
+
+    public function getTsConfig(): array
+    {
+        return $this->tsConfig;
+    }
+
+    public function addTsConfig(string $tsConfig): void
+    {
+        $this->tsConfig[] = $tsConfig;
+    }
+
+    public function setTsConfig(array $tsConfig): void
+    {
+        $this->tsConfig = $tsConfig;
+    }
+
+    public function getRootLine(): array
+    {
+        return $this->rootLine;
+    }
+}
diff --git a/typo3/sysext/core/Classes/TypoScript/IncludeTree/IncludeNode/TsConfigInclude.php b/typo3/sysext/core/Classes/TypoScript/IncludeTree/IncludeNode/TsConfigInclude.php
new file mode 100644
index 0000000000000000000000000000000000000000..3abbef82bee5239b3046890da3e208fd8338cdbd
--- /dev/null
+++ b/typo3/sysext/core/Classes/TypoScript/IncludeTree/IncludeNode/TsConfigInclude.php
@@ -0,0 +1,27 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode;
+
+/**
+ * An include type used by user and pages TsConfig for single TsConfig snippets.
+ *
+ * @internal: Internal tree structure.
+ */
+final class TsConfigInclude extends AbstractInclude
+{
+}
diff --git a/typo3/sysext/core/Classes/TypoScript/IncludeTree/TsConfigTreeBuilder.php b/typo3/sysext/core/Classes/TypoScript/IncludeTree/TsConfigTreeBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..0efa2734582a1b64af6ca6e51fc6aaa2caef2f26
--- /dev/null
+++ b/typo3/sysext/core/Classes/TypoScript/IncludeTree/TsConfigTreeBuilder.php
@@ -0,0 +1,190 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Core\TypoScript\IncludeTree;
+
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend;
+use TYPO3\CMS\Core\Configuration\Event\ModifyLoadedPageTsConfigEvent as LegacyModifyLoadedPageTsConfigEvent;
+use TYPO3\CMS\Core\EventDispatcher\EventDispatcher;
+use TYPO3\CMS\Core\Package\PackageManager;
+use TYPO3\CMS\Core\TypoScript\IncludeTree\Event\ModifyLoadedPageTsConfigEvent;
+use TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\RootInclude;
+use TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\TsConfigInclude;
+use TYPO3\CMS\Core\TypoScript\Tokenizer\TokenizerInterface;
+use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\PathUtility;
+
+/**
+ * Build include tree for UserTsConfig and PageTsConfig. This is typically used only by
+ * UserTsConfigFactory and PageTsConfigFactory.
+ *
+ * @internal
+ */
+final class TsConfigTreeBuilder
+{
+    private TokenizerInterface $tokenizer;
+    private ?PhpFrontend $cache = null;
+
+    public function __construct(
+        private readonly TreeFromLineStreamBuilder $treeFromTokenStreamBuilder,
+        private readonly PackageManager $packageManager,
+        private readonly EventDispatcher $eventDispatcher,
+    ) {
+    }
+
+    public function getUserTsConfigTree(
+        BackendUserAuthentication $backendUser,
+        TokenizerInterface $tokenizer,
+        ?PhpFrontend $cache = null
+    ): RootInclude {
+        $this->tokenizer = $tokenizer;
+        $this->cache = $cache;
+        $includeTree = new RootInclude();
+        if (!empty($GLOBALS['TYPO3_CONF_VARS']['BE']['defaultUserTSconfig'] ?? '')) {
+            $includeTree->addChild($this->getTreeFromString('userTsConfig-globals', $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultUserTSconfig']));
+        }
+        if ($backendUser->isAdmin()) {
+            // @todo: Could we maybe solve this differently somehow? Maybe in ext:adminpanel in FE directly?
+            $includeTree->addChild($this->getTreeFromString('userTsConfig-admpanel', 'admPanel.enable.all = 1'));
+        }
+        // @todo: Get rid of this, maybe by using a hook in DataHandler?
+        $includeTree->addChild($this->getTreeFromString(
+            'userTsConfig-sysnote',
+            'TCAdefaults.sys_note.author = ' . ($backendUser->user['realName'] ?? '') . chr(10) . 'TCAdefaults.sys_note.email = ' . ($backendUser->user['email'] ?? '')
+        ));
+        foreach ($backendUser->userGroupsUID as $groupId) {
+            // Loop through all groups and add their 'TSconfig' fields
+            if (!empty($backendUser->userGroups[$groupId]['TSconfig'] ?? '')) {
+                $includeTree->addChild($this->getTreeFromString('userTsConfig-group-' . $groupId, $backendUser->userGroups[$groupId]['TSconfig']));
+            }
+        }
+        if (!empty($backendUser->user['TSconfig'] ?? '')) {
+            $includeTree->addChild($this->getTreeFromString('userTsConfig-user', $backendUser->user['TSconfig']));
+        }
+        return $includeTree;
+    }
+
+    public function getPagesTsConfigTree(
+        array $rootLine,
+        TokenizerInterface $tokenizer,
+        ?PhpFrontend $cache = null
+    ): RootInclude {
+        $this->tokenizer = $tokenizer;
+        $this->cache = $cache;
+
+        $collectedPagesTsConfigArray = [];
+        $gotPackagesPagesTsConfigFromCache = false;
+        if ($cache) {
+            $collectedPagesTsConfigArrayFromCache = $cache->require('pagestsconfig-packages-strings');
+            if ($collectedPagesTsConfigArrayFromCache) {
+                $gotPackagesPagesTsConfigFromCache = true;
+                $collectedPagesTsConfigArray = $collectedPagesTsConfigArrayFromCache;
+            }
+        }
+        if (!$gotPackagesPagesTsConfigFromCache) {
+            foreach ($this->packageManager->getActivePackages() as $package) {
+                $packagePath = $package->getPackagePath();
+                $tsConfigFile = null;
+                if (file_exists($packagePath . 'Configuration/page.tsconfig')) {
+                    $tsConfigFile = $packagePath . 'Configuration/page.tsconfig';
+                } elseif (file_exists($packagePath . 'Configuration/Page.tsconfig')) {
+                    $tsConfigFile = $packagePath . 'Configuration/Page.tsconfig';
+                }
+                if ($tsConfigFile) {
+                    $typoScriptString = @file_get_contents($tsConfigFile);
+                    if (!empty($typoScriptString)) {
+                        $collectedPagesTsConfigArray['pagesTsConfig-package-' . $package->getPackageKey()] = $typoScriptString;
+                    }
+                }
+            }
+            $cache?->set('pagestsconfig-packages-strings', 'return unserialize(\'' . addcslashes(serialize($collectedPagesTsConfigArray), '\'\\') . '\');');
+        }
+
+        if (!empty($GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig'] ?? '')) {
+            $collectedPagesTsConfigArray['pagesTsConfig-globals-defaultPageTSconfig'] = $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig'];
+        }
+
+        foreach ($rootLine as $page) {
+            if (empty($page['uid'])) {
+                // Page 0 can happen when the rootline is given from BE context. It has not TSconfig. Skip this.
+                continue;
+            }
+            if (trim($page['tsconfig_includes'] ?? '')) {
+                $includeTsConfigFileList = GeneralUtility::trimExplode(',', $page['tsconfig_includes'], true);
+                foreach ($includeTsConfigFileList as $key => $includeTsConfigFile) {
+                    if (PathUtility::isExtensionPath($includeTsConfigFile)) {
+                        [$includeTsConfigFileExtensionKey, $includeTsConfigFilename] = explode('/', substr($includeTsConfigFile, 4), 2);
+                        if ($includeTsConfigFileExtensionKey !== ''
+                            && ExtensionManagementUtility::isLoaded($includeTsConfigFileExtensionKey)
+                            && $includeTsConfigFilename !== ''
+                        ) {
+                            $extensionPath = ExtensionManagementUtility::extPath($includeTsConfigFileExtensionKey);
+                            $includeTsConfigFileAndPath = PathUtility::getCanonicalPath($extensionPath . $includeTsConfigFilename);
+                            if (str_starts_with($includeTsConfigFileAndPath, $extensionPath) && file_exists($includeTsConfigFileAndPath)) {
+                                $typoScriptString = (string)file_get_contents($includeTsConfigFileAndPath);
+                                if (!empty($typoScriptString)) {
+                                    $collectedPagesTsConfigArray['pagesTsConfig-page-' . $page['uid'] . '-includes-' . $key] = (string)file_get_contents($includeTsConfigFileAndPath);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            if (!empty($page['TSconfig'])) {
+                $collectedPagesTsConfigArray['pagesTsConfig-page-' . $page['uid'] . '-tsConfig'] = $page['TSconfig'];
+            }
+        }
+
+        // @deprecated since TYPO3 v12, remove together with event class and set IncludeTree\Event\ModifyLoadedPageTsConfigEvent final in v13.
+        /** @var LegacyModifyLoadedPageTsConfigEvent $event */
+        $event = $this->eventDispatcher->dispatch(new LegacyModifyLoadedPageTsConfigEvent($collectedPagesTsConfigArray, $rootLine));
+        $collectedPagesTsConfigArray = $event->getTsConfig();
+
+        /** @var ModifyLoadedPageTsConfigEvent $event */
+        $event = $this->eventDispatcher->dispatch(new ModifyLoadedPageTsConfigEvent($collectedPagesTsConfigArray, $rootLine));
+        $collectedPagesTsConfigArray = $event->getTsConfig();
+
+        $includeTree = new RootInclude();
+        foreach ($collectedPagesTsConfigArray as $key => $typoScriptString) {
+            $includeTree->addChild($this->getTreeFromString((string)$key, $typoScriptString));
+        }
+        return $includeTree;
+    }
+
+    private function getTreeFromString(
+        string $name,
+        string $typoScriptString,
+    ): TsConfigInclude {
+        $lowercaseName = mb_strtolower($name);
+        $identifier = $lowercaseName . '-' . sha1($typoScriptString);
+        if ($this->cache) {
+            $includeNode = $this->cache->require($identifier);
+            if ($includeNode instanceof TsConfigInclude) {
+                return $includeNode;
+            }
+        }
+        $includeNode = new TsConfigInclude();
+        $includeNode->setName($name);
+        $includeNode->setIdentifier($lowercaseName);
+        $includeNode->setLineStream($this->tokenizer->tokenize($typoScriptString));
+        $this->treeFromTokenStreamBuilder->buildTree($includeNode, 'other', $this->tokenizer);
+        $this->cache?->set($identifier, 'return unserialize(\'' . addcslashes(serialize($includeNode), '\'\\') . '\');');
+        return $includeNode;
+    }
+}
diff --git a/typo3/sysext/core/Classes/TypoScript/PageTsConfig.php b/typo3/sysext/core/Classes/TypoScript/PageTsConfig.php
new file mode 100644
index 0000000000000000000000000000000000000000..d33886bb9939fdc5a256ced99dff09350a84cfcd
--- /dev/null
+++ b/typo3/sysext/core/Classes/TypoScript/PageTsConfig.php
@@ -0,0 +1,46 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Core\TypoScript;
+
+use TYPO3\CMS\Core\TypoScript\AST\Node\RootNode;
+
+/**
+ * A data object that carries the final PageTsConfig. This is created by PageTsConfigFactory.
+ *
+ * @internal Internal for now until API stabilized. Use BackendUtility::getPagesTSconfig().
+ */
+final class PageTsConfig
+{
+    private readonly array $pageTsConfigArray;
+
+    public function __construct(
+        private readonly RootNode $pageTsConfigTree
+    ) {
+        $this->pageTsConfigArray = $pageTsConfigTree->toArray();
+    }
+
+    public function getPageTsConfigTree(): RootNode
+    {
+        return $this->pageTsConfigTree;
+    }
+
+    public function getPageTsConfigArray(): array
+    {
+        return $this->pageTsConfigArray;
+    }
+}
diff --git a/typo3/sysext/core/Classes/TypoScript/PageTsConfigFactory.php b/typo3/sysext/core/Classes/TypoScript/PageTsConfigFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..a5089ecf025109a049e44f3a70f486b4603e320e
--- /dev/null
+++ b/typo3/sysext/core/Classes/TypoScript/PageTsConfigFactory.php
@@ -0,0 +1,132 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Core\TypoScript;
+
+use Psr\Container\ContainerInterface;
+use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend;
+use TYPO3\CMS\Core\Configuration\TypoScript\ConditionMatching\ConditionMatcherInterface;
+use TYPO3\CMS\Core\Site\Entity\Site;
+use TYPO3\CMS\Core\Site\Entity\SiteInterface;
+use TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\RootInclude;
+use TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\SiteInclude;
+use TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\TsConfigInclude;
+use TYPO3\CMS\Core\TypoScript\IncludeTree\Traverser\ConditionVerdictAwareIncludeTreeTraverser;
+use TYPO3\CMS\Core\TypoScript\IncludeTree\TsConfigTreeBuilder;
+use TYPO3\CMS\Core\TypoScript\IncludeTree\Visitor\IncludeTreeAstBuilderVisitor;
+use TYPO3\CMS\Core\TypoScript\IncludeTree\Visitor\IncludeTreeConditionMatcherVisitor;
+use TYPO3\CMS\Core\TypoScript\IncludeTree\Visitor\IncludeTreeSetupConditionConstantSubstitutionVisitor;
+use TYPO3\CMS\Core\TypoScript\Tokenizer\TokenizerInterface;
+
+/**
+ * Calculate PageTsConfig. This does the heavy lifting additionally supported by
+ * TsConfigTreeBuilder: Load basic pageTsConfig tree, overload with userTsConfig, parse
+ * site settings ("constants"), then build the PageTsConfig AST and return PageTsConfig DTO.
+ *
+ * @internal Internal for now until API stabilized. Use BackendUtility::getPagesTSconfig().
+ */
+final class PageTsConfigFactory
+{
+    public function __construct(
+        private readonly ContainerInterface $container,
+        private readonly TokenizerInterface $tokenizer,
+        private readonly TsConfigTreeBuilder $tsConfigTreeBuilder,
+        private readonly PhpFrontend $cache,
+    ) {
+    }
+
+    public function create(
+        array $rootLine,
+        SiteInterface $site,
+        ConditionMatcherInterface $conditionMatcher,
+        ?UserTsConfig $userTsConfig = null
+    ): PageTsConfig {
+        $pagesTsConfigTree = $this->tsConfigTreeBuilder->getPagesTsConfigTree($rootLine, $this->tokenizer, $this->cache);
+
+        // Overloading with userTsConfig if hand over
+        if ($userTsConfig instanceof UserTsConfig) {
+            $userTsConfigAst = $userTsConfig->getUserTsConfigTree();
+            $userTsConfigPageOverrides = '';
+            $userTsConfigFlat = $userTsConfigAst->flatten();
+            foreach ($userTsConfigFlat as $userTsConfigIdentifier => $userTsConfigValue) {
+                if (str_starts_with($userTsConfigIdentifier, 'page.')) {
+                    $userTsConfigPageOverrides .= substr($userTsConfigIdentifier, 5) . ' = ' . $userTsConfigValue . chr(10);
+                }
+            }
+            if (!empty($userTsConfigPageOverrides)) {
+                $includeNode = new TsConfigInclude();
+                $includeNode->setName('pageTsConfig-overrides-by-userTsConfig');
+                $includeNode->setIdentifier('pagetsconfig-overrides-by-usertsconfig');
+                $includeNode->setLineStream($this->tokenizer->tokenize($userTsConfigPageOverrides));
+                $pagesTsConfigTree->addChild($includeNode);
+            }
+        }
+
+        // Prepare site constants to be substituted
+        $includeTreeTraverserConditionVerdictAware = new ConditionVerdictAwareIncludeTreeTraverser();
+        $siteSettingsFlat = [];
+        if ($site instanceof Site) {
+            $siteSettings = $site->getSettings();
+            if (!$siteSettings->isEmpty()) {
+                $siteSettingsCacheIdentifier = 'site-settings-flat-' . sha1(json_encode($siteSettings, JSON_THROW_ON_ERROR));
+                $gotSiteSettingsFromCache = false;
+                $siteSettingsNode = $this->cache->require($siteSettingsCacheIdentifier);
+                if ($siteSettingsNode) {
+                    $gotSiteSettingsFromCache = true;
+                }
+                if (!$gotSiteSettingsFromCache) {
+                    $siteConstants = '';
+                    $siteSettings = $siteSettings->getAllFlat();
+                    foreach ($siteSettings as $nodeIdentifier => $value) {
+                        $siteConstants .= $nodeIdentifier . ' = ' . $value . LF;
+                    }
+                    $siteSettingsNode = new SiteInclude();
+                    $siteSettingsNode->setIdentifier($siteSettingsCacheIdentifier);
+                    $siteSettingsNode->setName('Site constants settings of site ' . $site->getIdentifier());
+                    $siteSettingsNode->setLineStream($this->tokenizer->tokenize($siteConstants));
+                    $siteSettingsTreeRoot = new RootInclude();
+                    $siteSettingsTreeRoot->addChild($siteSettingsNode);
+                    /** @var IncludeTreeAstBuilderVisitor $astBuilderVisitor */
+                    $astBuilderVisitor = $this->container->get(IncludeTreeAstBuilderVisitor::class);
+                    $includeTreeTraverserConditionVerdictAware->resetVisitors();
+                    $includeTreeTraverserConditionVerdictAware->addVisitor($astBuilderVisitor);
+                    $includeTreeTraverserConditionVerdictAware->traverse($siteSettingsTreeRoot);
+                    $siteSettingsFlat = $astBuilderVisitor->getAst()->flatten();
+                    $this->cache->set($siteSettingsCacheIdentifier, 'return unserialize(\'' . addcslashes(serialize($siteSettingsFlat), '\'\\') . '\');');
+                }
+            }
+        }
+
+        // Create AST with constants from site and conditions
+        $includeTreeTraverserConditionVerdictAware->resetVisitors();
+        if (!empty($siteSettingsFlat)) {
+            $setupConditionConstantSubstitutionVisitor = new IncludeTreeSetupConditionConstantSubstitutionVisitor();
+            $setupConditionConstantSubstitutionVisitor->setFlattenedConstants($siteSettingsFlat);
+            $includeTreeTraverserConditionVerdictAware->addVisitor($setupConditionConstantSubstitutionVisitor);
+        }
+        $conditionMatcherVisitor = new IncludeTreeConditionMatcherVisitor();
+        $conditionMatcherVisitor->setConditionMatcher($conditionMatcher);
+        $includeTreeTraverserConditionVerdictAware->addVisitor($conditionMatcherVisitor);
+        /** @var IncludeTreeAstBuilderVisitor $astBuilderVisitor */
+        $astBuilderVisitor = $this->container->get(IncludeTreeAstBuilderVisitor::class);
+        $astBuilderVisitor->setFlatConstants($siteSettingsFlat);
+        $includeTreeTraverserConditionVerdictAware->addVisitor($astBuilderVisitor);
+        $includeTreeTraverserConditionVerdictAware->traverse($pagesTsConfigTree);
+
+        return new PageTsConfig($astBuilderVisitor->getAst());
+    }
+}
diff --git a/typo3/sysext/core/Classes/TypoScript/Parser/TypoScriptParser.php b/typo3/sysext/core/Classes/TypoScript/Parser/TypoScriptParser.php
index 46ff1c6227e89d92cbfbe3c18c6691e33fa746ce..c1d913f7c78e255898155e70bb6f10243f981e63 100644
--- a/typo3/sysext/core/Classes/TypoScript/Parser/TypoScriptParser.php
+++ b/typo3/sysext/core/Classes/TypoScript/Parser/TypoScriptParser.php
@@ -32,8 +32,7 @@ use TYPO3\CMS\Frontend\Configuration\TypoScript\ConditionMatching\ConditionMatch
 /**
  * The TypoScript parser.
  *
- * @deprecated This class should not be used anymore, last core usages will be removed during v12.
- *             Using methods or properties of this class will start logging deprecation messages.
+ * @deprecated This class should not be used anymore. Switch to the new parser construct instead.
  */
 class TypoScriptParser
 {
@@ -150,6 +149,11 @@ class TypoScriptParser
      */
     public $lineNumberOffset = 0;
 
+    public function __construct()
+    {
+        trigger_error('Class ' . __CLASS__ . ' will be removed with TYPO3 v13.0. Use the new parser structure instead.', E_USER_DEPRECATED);
+    }
+
     /**
      * Start parsing the input TypoScript text piece. The result is stored in $this->setup
      *
diff --git a/typo3/sysext/core/Classes/TypoScript/TypoScriptStringFactory.php b/typo3/sysext/core/Classes/TypoScript/TypoScriptStringFactory.php
index 2d931643741c4e36ca3b93dd7aee7b113bb07867..230a0a96822bb8d1743b973569826b37468c8c5d 100644
--- a/typo3/sysext/core/Classes/TypoScript/TypoScriptStringFactory.php
+++ b/typo3/sysext/core/Classes/TypoScript/TypoScriptStringFactory.php
@@ -18,6 +18,7 @@ declare(strict_types=1);
 namespace TYPO3\CMS\Core\TypoScript;
 
 use Psr\Container\ContainerInterface;
+use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend;
 use TYPO3\CMS\Core\Configuration\TypoScript\ConditionMatching\ConditionMatcherInterface;
 use TYPO3\CMS\Core\TypoScript\AST\AstBuilderInterface;
@@ -38,6 +39,7 @@ final class TypoScriptStringFactory
 {
     public function __construct(
         private readonly ContainerInterface $container,
+        private readonly TokenizerInterface $tokenizer,
     ) {
     }
 
@@ -46,11 +48,15 @@ final class TypoScriptStringFactory
      *
      * @param non-empty-string $name A name used as cache identifier, [a-z,A-Z,-] only
      */
-    public function parseFromStringWithIncludesAndConditions(string $name, string $typoScript, TokenizerInterface $tokenizer, ConditionMatcherInterface $conditionMatcher, ?PhpFrontend $cache = null): RootNode
+    public function parseFromStringWithIncludesAndConditions(string $name, string $typoScript, ConditionMatcherInterface $conditionMatcher): RootNode
     {
+        /** @var CacheManager $cacheManager */
+        $cacheManager = $this->container->get(CacheManager::class);
+        /** @var PhpFrontend $cache */
+        $cache = $cacheManager->getCache('typoscript');
         /** @var StringTreeBuilder $stringTreeBuilder */
         $stringTreeBuilder = $this->container->get(StringTreeBuilder::class);
-        $includeTree = $stringTreeBuilder->getTreeFromString($name, $typoScript, $tokenizer, $cache);
+        $includeTree = $stringTreeBuilder->getTreeFromString($name, $typoScript, $this->tokenizer, $cache);
         $conditionMatcherVisitor = new IncludeTreeConditionMatcherVisitor();
         $conditionMatcherVisitor->setConditionMatcher($conditionMatcher);
         $includeTreeTraverserConditionVerdictAware = new ConditionVerdictAwareIncludeTreeTraverser();
@@ -64,13 +70,13 @@ final class TypoScriptStringFactory
 
     /**
      * Parse a single string *not* supporting imports, conditions and caching.
-     * Used in install tool context only.
+     * Detail method used in install tool and in a couple of other special cases.
      *
      * @internal
      */
-    public function parseFromString(string $typoScript, TokenizerInterface $tokenizer, AstBuilderInterface $astBuilder): RootNode
+    public function parseFromString(string $typoScript, AstBuilderInterface $astBuilder): RootNode
     {
-        $lineStream = $tokenizer->tokenize($typoScript);
+        $lineStream = $this->tokenizer->tokenize($typoScript);
         return $astBuilder->build($lineStream, new RootNode());
     }
 }
diff --git a/typo3/sysext/core/Classes/TypoScript/UserTsConfig.php b/typo3/sysext/core/Classes/TypoScript/UserTsConfig.php
new file mode 100644
index 0000000000000000000000000000000000000000..3617a20642f75ce5c2603774bb8c58e8700845a2
--- /dev/null
+++ b/typo3/sysext/core/Classes/TypoScript/UserTsConfig.php
@@ -0,0 +1,46 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Core\TypoScript;
+
+use TYPO3\CMS\Core\TypoScript\AST\Node\RootNode;
+
+/**
+ * A data object that carries the final UserTsConfig. This is created by UserTsConfigFactory.
+ *
+ * @internal Internal for now until API stabilized. Use backendUser->getTSConfig().
+ */
+final class UserTsConfig
+{
+    private readonly array $userTsConfigArray;
+
+    public function __construct(
+        private readonly RootNode $userTsConfigTree
+    ) {
+        $this->userTsConfigArray = $userTsConfigTree->toArray();
+    }
+
+    public function getUserTsConfigTree(): RootNode
+    {
+        return $this->userTsConfigTree;
+    }
+
+    public function getUserTsConfigArray(): array
+    {
+        return $this->userTsConfigArray;
+    }
+}
diff --git a/typo3/sysext/core/Classes/TypoScript/UserTsConfigFactory.php b/typo3/sysext/core/Classes/TypoScript/UserTsConfigFactory.php
new file mode 100644
index 0000000000000000000000000000000000000000..d97d75f3c2ab41685ab05ea798642f5e1d0dc18f
--- /dev/null
+++ b/typo3/sysext/core/Classes/TypoScript/UserTsConfigFactory.php
@@ -0,0 +1,63 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Core\TypoScript;
+
+use Psr\Container\ContainerInterface;
+use TYPO3\CMS\Backend\Configuration\TypoScript\ConditionMatching\ConditionMatcher as BackendConditionMatcher;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend;
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\TypoScript\IncludeTree\Traverser\ConditionVerdictAwareIncludeTreeTraverser;
+use TYPO3\CMS\Core\TypoScript\IncludeTree\TsConfigTreeBuilder;
+use TYPO3\CMS\Core\TypoScript\IncludeTree\Visitor\IncludeTreeAstBuilderVisitor;
+use TYPO3\CMS\Core\TypoScript\IncludeTree\Visitor\IncludeTreeConditionMatcherVisitor;
+use TYPO3\CMS\Core\TypoScript\Tokenizer\TokenizerInterface;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Calculate UserTsConfig. This does the heavy lifting additionally supported by
+ * TsConfigTreeBuilder: Load basic userTsConfig tree, then build the UserTsConfig AST
+ * and return UserTsConfig DTO.
+ *
+ * @internal Internal for now until API stabilized. Use backendUser->getTSConfig().
+ */
+final class UserTsConfigFactory
+{
+    public function __construct(
+        private readonly ContainerInterface $container,
+        private readonly TokenizerInterface $tokenizer,
+        private readonly TsConfigTreeBuilder $tsConfigTreeBuilder,
+        private readonly PhpFrontend $cache,
+    ) {
+    }
+
+    public function create(BackendUserAuthentication $backendUser): UserTsConfig
+    {
+        $includeTreeTraverserConditionVerdictAware = new ConditionVerdictAwareIncludeTreeTraverser();
+        $userTsConfigTree = $this->tsConfigTreeBuilder->getUserTsConfigTree($backendUser, $this->tokenizer, $this->cache);
+        $conditionMatcherVisitor = new IncludeTreeConditionMatcherVisitor();
+        $backendConditionMatcher = GeneralUtility::makeInstance(BackendConditionMatcher::class, GeneralUtility::makeInstance(Context::class));
+        $conditionMatcherVisitor->setConditionMatcher($backendConditionMatcher);
+        $includeTreeTraverserConditionVerdictAware->addVisitor($conditionMatcherVisitor);
+        /** @var IncludeTreeAstBuilderVisitor $astBuilderVisitor */
+        $astBuilderVisitor = $this->container->get(IncludeTreeAstBuilderVisitor::class);
+        $includeTreeTraverserConditionVerdictAware->addVisitor($astBuilderVisitor);
+        $includeTreeTraverserConditionVerdictAware->traverse($userTsConfigTree);
+        return new UserTsConfig($astBuilderVisitor->getAst());
+    }
+}
diff --git a/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php b/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php
index 2bbe898daf3c2fa98f8772cb6169bb61c5d0b18e..2015d683e892999b82ef27fbe33c792375b83b46 100644
--- a/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php
+++ b/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php
@@ -732,9 +732,7 @@ class ExtensionManagementUtility
      */
     public static function addPageTSConfig(string $content): void
     {
-        $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig'] .= '
-[GLOBAL]
-' . $content;
+        $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig'] .= chr(10) . $content;
     }
 
     /**
@@ -746,9 +744,7 @@ class ExtensionManagementUtility
      */
     public static function addUserTSConfig(string $content): void
     {
-        $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultUserTSconfig'] .= '
-[GLOBAL]
-' . $content;
+        $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultUserTSconfig'] .= chr(10) . $content;
     }
 
     /**
diff --git a/typo3/sysext/core/Configuration/Services.yaml b/typo3/sysext/core/Configuration/Services.yaml
index e945bef45fe8f06f8f03ca56a3c19b4178b97c35..9fc1cd587901dfc13d95b84ba4f64973f97f9de0 100644
--- a/typo3/sysext/core/Configuration/Services.yaml
+++ b/typo3/sysext/core/Configuration/Services.yaml
@@ -57,14 +57,17 @@ services:
   TYPO3\CMS\Core\Http\MiddlewareDispatcher:
     autoconfigure: false
 
+  # @deprecated since v12, will be removed with v13 together with class PageTsConfigLoader.
   TYPO3\CMS\Core\Configuration\Loader\PageTsConfigLoader:
     public: true
 
+  # @deprecated since v12, will be removed with v13 together with class PageTsConfigParser.
   TYPO3\CMS\Core\Configuration\Parser\PageTsConfigParser:
     public: true
     arguments:
       $cache: '@cache.hash'
 
+  # @deprecated since v12, will be removed with v13 together with class PageTsConfig.
   TYPO3\CMS\Core\Configuration\PageTsConfig:
     public: true
     arguments:
@@ -324,6 +327,9 @@ services:
   TYPO3\CMS\Core\TypoScript\IncludeTree\SysTemplateTreeBuilder:
     public: true
 
+  TYPO3\CMS\Core\TypoScript\IncludeTree\TsConfigTreeBuilder:
+    public: true
+
   TYPO3\CMS\Core\TypoScript\IncludeTree\Visitor\IncludeTreeAstBuilderVisitor:
     public: true
     # Ast builder visitor creates state and should not be re-used
@@ -337,6 +343,16 @@ services:
   TYPO3\CMS\Core\TypoScript\Tokenizer\TokenizerInterface:
     alias: TYPO3\CMS\Core\TypoScript\Tokenizer\LossyTokenizer
 
+  TYPO3\CMS\Core\TypoScript\PageTsConfigFactory:
+    public: true
+    arguments:
+      $cache: '@cache.typoscript'
+
+  TYPO3\CMS\Core\TypoScript\UserTsConfigFactory:
+    public: true
+    arguments:
+      $cache: '@cache.typoscript'
+
   # Core caches, cache.core and cache.assets are injected as early
   # entries in TYPO3\CMS\Core\Core\Bootstrap and therefore omitted here
   cache.hash:
diff --git a/typo3/sysext/core/Documentation/Changelog/12.2/Deprecation-99120-DeprecateOldTypoScriptParser.rst b/typo3/sysext/core/Documentation/Changelog/12.2/Deprecation-99120-DeprecateOldTypoScriptParser.rst
new file mode 100644
index 0000000000000000000000000000000000000000..6514700a602c9d9873af456ea0f30bf0a1633943
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/12.2/Deprecation-99120-DeprecateOldTypoScriptParser.rst
@@ -0,0 +1,109 @@
+.. include:: /Includes.rst.txt
+
+.. _deprecation-99120-1670428555:
+
+====================================================
+Deprecation: #99120 - Deprecate old TypoScriptParser
+====================================================
+
+See :issue:`99120`
+
+Description
+===========
+
+To phase out usages of the old TypoScript parser by switching to the
+:ref:`new parser approach <breaking-97816-1664800747>`, a couple of classes
+and methods have been marked as deprecated in TYPO3 v12 that will be removed with v13:
+
+* Class :php:`\TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser`
+* Class :php:`\TYPO3\CMS\Core\Configuration\Loader\PageTsConfigLoader`
+* Class :php:`\TYPO3\CMS\Core\Configuration\PageTsConfig`
+* Class :php:`\TYPO3\CMS\Core\Configuration\Parser\PageTsConfigParser`
+* Event :php:`\TYPO3\CMS\Core\Configuration\Event\ModifyLoadedPageTsConfigEvent`
+* Method :php:`\TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->getPagesTSconfig()`
+
+The existing main API to retrieve PageTsConfig using :php:`\TYPO3\CMS\Backend\Utility\BackendUtility::getPagesTSconfig()`
+and UserTsConfig using :php:`$backendUser->getTSConfig()` is kept.
+
+
+Impact
+======
+
+Using one of the above classes will raise a deprecation level log entry and
+will stop working with TYPO3 v13.
+
+
+Affected installations
+======================
+
+Instances with extensions that use one of the above classes are affected. The
+extension scanner will find usages with a mixture of weak and strong matches
+depending on the usage.
+
+Most deprecations are rather "internal" since extensions most likely use the
+existing outer API already. Some may be affected when using the
+:php:`\TYPO3\CMS\Core\Configuration\Event\ModifyLoadedPageTsConfigEvent` event
+or the frontend related method :php:`TypoScriptFrontendController->getPagesTSconfig()`,
+though.
+
+
+Migration
+=========
+
+:php:`\TYPO3\CMS\Core\Configuration\Event\ModifyLoadedPageTsConfigEvent`
+------------------------------------------------------------------------
+
+This event is consumed by some extensions to modify calculated PageTsConfig strings
+before parsing. The event moved its namespace from
+:php:`\TYPO3\CMS\Core\Configuration\Event\ModifyLoadedPageTsConfigEvent` to
+:php:`\TYPO3\CMS\Core\TypoScript\IncludeTree\Event\ModifyLoadedPageTsConfigEvent` in
+TYPO3 v12, but is kept as-is apart from that. The TYPO3 v12 core triggers *both* the old
+and the new event, and TYPO3 v13 will stop calling the old event.
+
+Extension that want to stay compatible with both TYPO3 v11 and v12 should continue to
+implement listen for the old event only. This will *not* raise a deprecation level log
+entry in v12, but it will stop working with TYPO3 v13.
+Extensions with compatibility for TYPO3 12 and above should switch to the new event.
+
+:php:`\TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->getPagesTSconfig()`
+--------------------------------------------------------------------------------------
+
+The TYPO3 Frontend should usually not need to retrieve Backend related PageTsConfig.
+Extensions using the method should avoid relying on bringing Backend related configuration
+into Frontend scope. However, the TYPO3 core comes with one place that does this: Class
+:php:`TYPO3\CMS\Frontend\Typolink\DatabaseRecordLinkBuilder` uses PageTsConfig related
+information in Frontend scope. Extensions with similar use cases could have a similar
+implementation as done with :php:`DatabaseRecordLinkBuilder->getPageTsConfig()`. Note
+any implementation of this will have to rely on :php:`@internal` usages of the new
+TypoScript parser approach, and using this low level API may thus break without
+further notice. Extensions are encouraged to cover usages with functional tests to find
+issues quickly in case the TYPO3 core still changes used classes.
+
+:php:`\TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser`
+---------------------------------------------------------
+
+In general, extensions probably don't need to use the old :php:`TypoScriptParser`
+often: Frontend TypoScript is :ref:`available as request attribute <deprecation-99020-1667911024>`,
+pageTsConfig should be retrieved using :php:`BackendUtility::getPagesTSconfig()` and
+userTsConfig should be retrieved using :php:`$backendUser->getTSConfig()`.
+
+In case extensions want to parse any other strings that follow a TypoScript-a-like syntax,
+they can use :php:`\TYPO3\CMS\Core\TypoScript\TypoScriptStringFactory`, or could set up
+their own factory using the new parser classes for more complex scenarios. Note the new parser
+approach is still marked :php:`@internal`, using this low level API may thus break without
+further notice. Extensions are encouraged to cover usages with functional tests to find
+issues quickly in case the TYPO3 core still changes used classes.
+
+:php:`\TYPO3\CMS\Core\Configuration\PageTsConfig`
+-------------------------------------------------
+
+There is little need to use :php:`\TYPO3\CMS\Core\Configuration\PageTsConfig` and their helper
+classes :php:`\TYPO3\CMS\Core\Configuration\Loader\PageTsConfigLoader` and
+:php:`\TYPO3\CMS\Core\Configuration\Parser\PageTsConfigParser` directly: The main API
+in Backend context is :php:`\TYPO3\CMS\Backend\Utility\BackendUtility::getPagesTSconfig()`.
+
+See the hint on :php:`\TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->getPagesTSconfig()`
+above for notes on how to migrate usages in Frontend context.
+
+
+.. index:: PHP-API, TSConfig, TypoScript, FullyScanned, ext:core
diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Classes/EventListener/LegacyModifyLoadedPageTsConfig.php b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Classes/EventListener/LegacyModifyLoadedPageTsConfig.php
new file mode 100644
index 0000000000000000000000000000000000000000..8ae065ec3def20da186f37ddceccdf8a81eea03d
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Classes/EventListener/LegacyModifyLoadedPageTsConfig.php
@@ -0,0 +1,31 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3Tests\TestTyposcriptPagetsconfigfactory\EventListener;
+
+use TYPO3\CMS\Core\Configuration\Event\ModifyLoadedPageTsConfigEvent;
+
+/**
+ * @deprecated since v12, remove along with 'legacy' event class in v13.
+ */
+final class LegacyModifyLoadedPageTsConfig
+{
+    public function __invoke(ModifyLoadedPageTsConfigEvent $event): void
+    {
+        $event->addTsConfig('loadedFromLegacyEvent = loadedFromLegacyEvent');
+    }
+}
diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Classes/EventListener/ModifyLoadedPageTsConfig.php b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Classes/EventListener/ModifyLoadedPageTsConfig.php
new file mode 100644
index 0000000000000000000000000000000000000000..6bf69048f2667d6579b58a9a434e163054689a1a
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Classes/EventListener/ModifyLoadedPageTsConfig.php
@@ -0,0 +1,28 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3Tests\TestTyposcriptPagetsconfigfactory\EventListener;
+
+use TYPO3\CMS\Core\TypoScript\IncludeTree\Event\ModifyLoadedPageTsConfigEvent;
+
+final class ModifyLoadedPageTsConfig
+{
+    public function __invoke(ModifyLoadedPageTsConfigEvent $event): void
+    {
+        $event->addTsConfig('loadedFromEvent = loadedFromEvent');
+    }
+}
diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Configuration/Services.yaml b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Configuration/Services.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..487c40bc412784873602ff9e0d53b766013e5e8d
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Configuration/Services.yaml
@@ -0,0 +1,16 @@
+services:
+  _defaults:
+    autowire: true
+    autoconfigure: true
+    public: false
+
+  TYPO3Tests\TestTyposcriptPagetsconfigfactory\EventListener\ModifyLoadedPageTsConfig:
+    tags:
+      - name: event.listener
+        identifier: 'typo3tests-test-typoscript-pagetsconfigfactory-event/modify-loaded-page-tsconfig'
+
+  # @deprecated since v12, remove along with 'legacy' event class in v13.
+  TYPO3Tests\TestTyposcriptPagetsconfigfactory\EventListener\LegacyModifyLoadedPageTsConfig:
+    tags:
+      - name: event.listener
+        identifier: 'typo3tests-test-typoscript-pagetsconfigfactory-event/legacy-modify-loaded-page-tsconfig'
diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Configuration/TsConfig/tsconfig-includes.tsconfig b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Configuration/TsConfig/tsconfig-includes.tsconfig
new file mode 100644
index 0000000000000000000000000000000000000000..c9aeea6c6b17e74c6f32e14e4f89b11b545d6f81
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Configuration/TsConfig/tsconfig-includes.tsconfig
@@ -0,0 +1 @@
+loadedFromTsconfigIncludes = loadedFromTsconfigIncludes
diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Configuration/page.tsconfig b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Configuration/page.tsconfig
new file mode 100644
index 0000000000000000000000000000000000000000..697c871653184f6a60c0f3120e36b97034a4909a
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Configuration/page.tsconfig
@@ -0,0 +1 @@
+loadedFromTestExtensionConfigurationPageTsConfig = loadedFromTestExtensionConfigurationPageTsConfig
diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/ext_emconf.php b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/ext_emconf.php
new file mode 100644
index 0000000000000000000000000000000000000000..b37805b84e4d70e2ef09cb15016644841af071c6
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/ext_emconf.php
@@ -0,0 +1,21 @@
+<?php
+
+declare(strict_types=1);
+
+$EM_CONF[$_EXTKEY] = [
+    'title' => 'TypoScript PageTsConfigFactory extension test',
+    'description' => 'TypoScript PageTsConfigFactory extension test',
+    'category' => 'example',
+    'version' => '12.2.0',
+    'state' => 'beta',
+    'author' => 'Christian Kuhn',
+    'author_email' => 'lolli@schwarzbu.ch',
+    'author_company' => '',
+    'constraints' => [
+        'depends' => [
+            'typo3' => '12.2.0',
+        ],
+        'conflicts' => [],
+        'suggests' => [],
+    ],
+];
diff --git a/typo3/sysext/core/Tests/Functional/TypoScript/Fixtures/pageTsConfigTestFixture.csv b/typo3/sysext/core/Tests/Functional/TypoScript/Fixtures/pageTsConfigTestFixture.csv
new file mode 100644
index 0000000000000000000000000000000000000000..925850b570b80a8255c5f03a388c4a1ed42786a9
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/TypoScript/Fixtures/pageTsConfigTestFixture.csv
@@ -0,0 +1,3 @@
+"be_users"
+,"uid","pid","username","admin","usergroup","TSconfig"
+,1,0,"user1",0,,"page.valueOverriddenByUserTsConfig = overridden"
diff --git a/typo3/sysext/core/Tests/Functional/TypoScript/Fixtures/userTsConfigTestFixture.csv b/typo3/sysext/core/Tests/Functional/TypoScript/Fixtures/userTsConfigTestFixture.csv
new file mode 100644
index 0000000000000000000000000000000000000000..65c652159cca97ab9e600d8d97a052ed37ffc00c
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/TypoScript/Fixtures/userTsConfigTestFixture.csv
@@ -0,0 +1,15 @@
+"be_users"
+,"uid","pid","username","admin","usergroup","TSconfig"
+,1,0,"user1",0,,
+,2,0,"user2",0,,"loadedFromUser = loadedFromUser"
+,3,0,"user3",0,"1",
+,4,0,"user4",0,"1,2",
+,5,0,"user5",1,,"isHttps = off
+[request.getNormalizedParams().isHttps()]
+  isHttps = on
+[end]
+"
+"be_groups"
+,"uid","pid","title","TSconfig"
+,1,0,"group1","loadedFromUserGroup = loadedFromUserGroup"
+,2,0,"group2","loadedFromUserGroup = loadedFromUserGroupOverride"
diff --git a/typo3/sysext/core/Tests/Functional/TypoScript/PageTsConfigFactoryTest.php b/typo3/sysext/core/Tests/Functional/TypoScript/PageTsConfigFactoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..b81ecdcd4dd8c31bcc60cf5e1bf973bb69b18383
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/TypoScript/PageTsConfigFactoryTest.php
@@ -0,0 +1,224 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Core\Tests\Functional\TypoScript;
+
+use TYPO3\CMS\Backend\Configuration\TypoScript\ConditionMatching\ConditionMatcher;
+use TYPO3\CMS\Core\Http\NormalizedParams;
+use TYPO3\CMS\Core\Http\ServerRequest;
+use TYPO3\CMS\Core\Site\Entity\NullSite;
+use TYPO3\CMS\Core\Site\Entity\Site;
+use TYPO3\CMS\Core\Site\Entity\SiteSettings;
+use TYPO3\CMS\Core\TypoScript\PageTsConfigFactory;
+use TYPO3\CMS\Core\TypoScript\UserTsConfigFactory;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
+
+/**
+ * Tests PageTsConfigFactory and indirectly IncludeTree/TsConfigTreeBuilder
+ */
+class PageTsConfigFactoryTest extends FunctionalTestCase
+{
+    protected array $testExtensionsToLoad = [
+        'typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory',
+    ];
+
+    /**
+     * @test
+     */
+    public function pageTsConfigLoadsDefaultsFromGlobals(): void
+    {
+        $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig'] = 'loadedFromGlobals = loadedFromGlobals';
+        /** @var PageTsConfigFactory $subject */
+        $subject = $this->get(PageTsConfigFactory::class);
+        $pageTsConfig = $subject->create([], new NullSite(), new ConditionMatcher());
+        self::assertSame('loadedFromGlobals', $pageTsConfig->getPageTsConfigArray()['loadedFromGlobals']);
+    }
+
+    /**
+     * @test
+     */
+    public function pageTsConfigLoadsFromTestExtensionConfigurationFile(): void
+    {
+        /** @var PageTsConfigFactory $subject */
+        $subject = $this->get(PageTsConfigFactory::class);
+        $pageTsConfig = $subject->create([], new NullSite(), new ConditionMatcher());
+        self::assertSame('loadedFromTestExtensionConfigurationPageTsConfig', $pageTsConfig->getPageTsConfigArray()['loadedFromTestExtensionConfigurationPageTsConfig']);
+    }
+
+    /**
+     * @test
+     */
+    public function pageTsConfigLoadsFromPagesTestExtensionConfigurationFile(): void
+    {
+        /** @var PageTsConfigFactory $subject */
+        $subject = $this->get(PageTsConfigFactory::class);
+        $pageTsConfig = $subject->create([], new NullSite(), new ConditionMatcher());
+        self::assertSame('loadedFromTestExtensionConfigurationPageTsConfig', $pageTsConfig->getPageTsConfigArray()['loadedFromTestExtensionConfigurationPageTsConfig']);
+    }
+
+    /**
+     * @test
+     */
+    public function pageTsConfigLoadsFromPageRecordTsconfigField(): void
+    {
+        $rootLine = [
+            [
+                'uid' => 1,
+                'TSconfig' => 'loadedFromTsConfigField = loadedFromTsConfigField',
+            ],
+        ];
+        /** @var PageTsConfigFactory $subject */
+        $subject = $this->get(PageTsConfigFactory::class);
+        $pageTsConfig = $subject->create($rootLine, new NullSite(), new ConditionMatcher());
+        self::assertSame('loadedFromTsConfigField', $pageTsConfig->getPageTsConfigArray()['loadedFromTsConfigField']);
+    }
+
+    /**
+     * @test
+     */
+    public function pageTsConfigLoadsFromPageRecordTsconfigFieldOverridesByLowerLevel(): void
+    {
+        $rootLine = [
+            [
+                'uid' => 1,
+                'TSconfig' => 'loadedFromTsConfigField1 = loadedFromTsConfigField1'
+                    . chr(10) . 'loadedFromTsConfigField2 = loadedFromTsConfigField2',
+            ],
+            [
+                'uid' => 2,
+                'TSconfig' => 'loadedFromTsConfigField1 = loadedFromTsConfigField1'
+                    . chr(10) . 'loadedFromTsConfigField2 = loadedFromTsConfigField2Override',
+            ],
+        ];
+        /** @var PageTsConfigFactory $subject */
+        $subject = $this->get(PageTsConfigFactory::class);
+        $pageTsConfig = $subject->create($rootLine, new NullSite(), new ConditionMatcher());
+        self::assertSame('loadedFromTsConfigField1', $pageTsConfig->getPageTsConfigArray()['loadedFromTsConfigField1']);
+        self::assertSame('loadedFromTsConfigField2Override', $pageTsConfig->getPageTsConfigArray()['loadedFromTsConfigField2']);
+    }
+
+    /**
+     * @test
+     */
+    public function pageTsConfigSubstitutesSettingsFromSite(): void
+    {
+        $rootLine = [
+            [
+                'uid' => 1,
+                'TSconfig' => 'siteSetting = {$aSiteSetting}',
+            ],
+        ];
+        /** @var PageTsConfigFactory $subject */
+        $subject = $this->get(PageTsConfigFactory::class);
+        $siteSettings = new SiteSettings(['aSiteSetting' => 'aSiteSettingValue']);
+        $site = new Site('siteIdentifier', 1, [], $siteSettings);
+        $pageTsConfig = $subject->create($rootLine, $site, new ConditionMatcher());
+        self::assertSame('aSiteSettingValue', $pageTsConfig->getPageTsConfigArray()['siteSetting']);
+    }
+
+    /**
+     * @test
+     */
+    public function pageTsConfigMatchesRequestHttpsCondition(): void
+    {
+        $request = (new ServerRequest('https://www.example.com/', null, 'php://input', [], ['HTTPS' => 'ON']));
+        $request = $request->withAttribute('normalizedParams', NormalizedParams::createFromRequest($request));
+        $GLOBALS['TYPO3_REQUEST'] = $request;
+        $rootLine = [
+            [
+                'uid' => 1,
+                'TSconfig' => 'isHttps = off'
+                    . chr(10) . '[request.getNormalizedParams().isHttps()]'
+                    . chr(10) . '  isHttps = on'
+                    . chr(10) . '[end]',
+            ],
+        ];
+        /** @var PageTsConfigFactory $subject */
+        $subject = $this->get(PageTsConfigFactory::class);
+        $pageTsConfig = $subject->create($rootLine, new NullSite(), new ConditionMatcher());
+        self::assertSame('on', $pageTsConfig->getPageTsConfigArray()['isHttps']);
+    }
+
+    /**
+     * @test
+     */
+    public function pageTsConfigMatchesRequestHttpsConditionUsingSiteConstant(): void
+    {
+        $request = (new ServerRequest('https://www.example.com/', null, 'php://input', [], ['HTTPS' => 'ON']));
+        $request = $request->withAttribute('normalizedParams', NormalizedParams::createFromRequest($request));
+        $GLOBALS['TYPO3_REQUEST'] = $request;
+        $rootLine = [
+            [
+                'uid' => 1,
+                'TSconfig' => 'isHttps = off'
+                    . chr(10) . '[{$aSiteSetting}]'
+                    . chr(10) . '  isHttps = on'
+                    . chr(10) . '[end]',
+            ],
+        ];
+        /** @var PageTsConfigFactory $subject */
+        $subject = $this->get(PageTsConfigFactory::class);
+        $siteSettings = new SiteSettings(['aSiteSetting' => 'request.getNormalizedParams().isHttps()']);
+        $site = new Site('siteIdentifier', 1, [], $siteSettings);
+        $pageTsConfig = $subject->create($rootLine, $site, new ConditionMatcher());
+        self::assertSame('on', $pageTsConfig->getPageTsConfigArray()['isHttps']);
+    }
+
+    /**
+     * @test
+     */
+    public function pageTsConfigLoadsFromEvent(): void
+    {
+        /** @var PageTsConfigFactory $subject */
+        $subject = $this->get(PageTsConfigFactory::class);
+        $pageTsConfig = $subject->create([], new NullSite(), new ConditionMatcher());
+        self::assertSame('loadedFromEvent', $pageTsConfig->getPageTsConfigArray()['loadedFromEvent']);
+    }
+
+    /**
+     * @test
+     */
+    public function pageTsConfigLoadsFromLegacyEvent(): void
+    {
+        /** @var PageTsConfigFactory $subject */
+        $subject = $this->get(PageTsConfigFactory::class);
+        $pageTsConfig = $subject->create([], new NullSite(), new ConditionMatcher());
+        self::assertSame('loadedFromLegacyEvent', $pageTsConfig->getPageTsConfigArray()['loadedFromLegacyEvent']);
+    }
+
+    /**
+     * @test
+     */
+    public function pageTsConfigCanBeOverloadedWithUserTsConfig(): void
+    {
+        $this->importCSVDataSet(__DIR__ . '/Fixtures/pageTsConfigTestFixture.csv');
+        $backendUser = $this->setUpBackendUser(1);
+        /** @var UserTsConfigFactory $userTsConfigFactory */
+        $userTsConfigFactory = $this->get(UserTsConfigFactory::class);
+        $userTsConfig = $userTsConfigFactory->create($backendUser);
+        $rootLine = [
+            [
+                'uid' => 1,
+                'TSconfig' => 'valueOverriddenByUserTsConfig = base',
+            ],
+        ];
+        /** @var PageTsConfigFactory $subject */
+        $subject = $this->get(PageTsConfigFactory::class);
+        $pageTsConfig = $subject->create($rootLine, new NullSite(), new ConditionMatcher(), $userTsConfig);
+        self::assertSame('overridden', $pageTsConfig->getPageTsConfigArray()['valueOverriddenByUserTsConfig']);
+    }
+}
diff --git a/typo3/sysext/core/Tests/Functional/TypoScript/TypoScriptStringFactoryTest.php b/typo3/sysext/core/Tests/Functional/TypoScript/TypoScriptStringFactoryTest.php
index 3c7fb9e14b13ad29f3cb0c267d7244d674be91dc..92febd8aa637d134cf6a84d5d1ae38062b209bf0 100644
--- a/typo3/sysext/core/Tests/Functional/TypoScript/TypoScriptStringFactoryTest.php
+++ b/typo3/sysext/core/Tests/Functional/TypoScript/TypoScriptStringFactoryTest.php
@@ -22,7 +22,6 @@ use TYPO3\CMS\Core\EventDispatcher\NoopEventDispatcher;
 use TYPO3\CMS\Core\TypoScript\AST\AstBuilder;
 use TYPO3\CMS\Core\TypoScript\AST\Node\ChildNode;
 use TYPO3\CMS\Core\TypoScript\AST\Node\RootNode;
-use TYPO3\CMS\Core\TypoScript\Tokenizer\LossyTokenizer;
 use TYPO3\CMS\Core\TypoScript\TypoScriptStringFactory;
 use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
 
@@ -47,7 +46,6 @@ class TypoScriptStringFactoryTest extends FunctionalTestCase
         $result = $subject->parseFromStringWithIncludesAndConditions(
             'testing',
             '@import \'EXT:core/Tests/Functional/TypoScript/Fixtures/SimpleCondition.typoscript\'',
-            new LossyTokenizer(),
             new BackendConditionMatcher()
         );
         self::assertEquals($expected, $result);
@@ -64,11 +62,7 @@ class TypoScriptStringFactoryTest extends FunctionalTestCase
         $expected->addChild($fooNode);
         /** @var TypoScriptStringFactory $subject */
         $subject = $this->get(TypoScriptStringFactory::class);
-        $result = $subject->parseFromString(
-            'foo = bar',
-            new LossyTokenizer(),
-            new AstBuilder(new NoopEventDispatcher())
-        );
+        $result = $subject->parseFromString('foo = bar', new AstBuilder(new NoopEventDispatcher()));
         self::assertEquals($expected, $result);
     }
 }
diff --git a/typo3/sysext/core/Tests/Functional/TypoScript/UserTsConfigFactoryTest.php b/typo3/sysext/core/Tests/Functional/TypoScript/UserTsConfigFactoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..f00bd62405c46245a684e49122f278f0d3561cdc
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/TypoScript/UserTsConfigFactoryTest.php
@@ -0,0 +1,104 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Core\Tests\Functional\TypoScript;
+
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Http\NormalizedParams;
+use TYPO3\CMS\Core\Http\ServerRequest;
+use TYPO3\CMS\Core\TypoScript\UserTsConfigFactory;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
+
+/**
+ * Tests UserTsConfigFactory and indirectly IncludeTree/TsConfigTreeBuilder
+ */
+class UserTsConfigFactoryTest extends FunctionalTestCase
+{
+    /**
+     * @test
+     */
+    public function userTsConfigLoadsDefaultFromGlobals(): void
+    {
+        $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultUserTSconfig'] = 'loadedFromGlobals = loadedFromGlobals';
+        $this->importCSVDataSet(__DIR__ . '/Fixtures/userTsConfigTestFixture.csv');
+        $backendUser = $this->setUpBackendUser(1);
+        /** @var UserTsConfigFactory $subject */
+        $subject = $this->get(UserTsConfigFactory::class);
+        $userTsConfig = $subject->create($backendUser);
+        self::assertSame('loadedFromGlobals', $userTsConfig->getUserTsConfigArray()['loadedFromGlobals']);
+    }
+
+    /**
+     * @test
+     */
+    public function userTsConfigLoadsDefaultFromBackendUserTsConfigField(): void
+    {
+        $this->importCSVDataSet(__DIR__ . '/Fixtures/userTsConfigTestFixture.csv');
+        $backendUser = $this->setUpBackendUser(2);
+        /** @var UserTsConfigFactory $subject */
+        $subject = $this->get(UserTsConfigFactory::class);
+        $userTsConfig = $subject->create($backendUser);
+        self::assertSame('loadedFromUser', $userTsConfig->getUserTsConfigArray()['loadedFromUser']);
+    }
+
+    /**
+     * @test
+     */
+    public function userTsConfigLoadsDefaultFromBackendUserGroupTsConfigField(): void
+    {
+        $this->importCSVDataSet(__DIR__ . '/Fixtures/userTsConfigTestFixture.csv');
+        $backendUser = $this->setUpBackendUser(3);
+        /** @var UserTsConfigFactory $subject */
+        $subject = $this->get(UserTsConfigFactory::class);
+        $userTsConfig = $subject->create($backendUser);
+        self::assertSame('loadedFromUserGroup', $userTsConfig->getUserTsConfigArray()['loadedFromUserGroup']);
+    }
+
+    /**
+     * @test
+     */
+    public function userTsConfigLoadsDefaultFromBackendUserGroupTsConfigFieldAndGroupOverride(): void
+    {
+        $this->importCSVDataSet(__DIR__ . '/Fixtures/userTsConfigTestFixture.csv');
+        $backendUser = $this->setUpBackendUser(4);
+        /** @var UserTsConfigFactory $subject */
+        $subject = $this->get(UserTsConfigFactory::class);
+        $userTsConfig = $subject->create($backendUser);
+        self::assertSame('loadedFromUserGroupOverride', $userTsConfig->getUserTsConfigArray()['loadedFromUserGroup']);
+    }
+
+    /**
+     * @test
+     */
+    public function userTsConfigMatchesRequestHttpsCondition(): void
+    {
+        $this->importCSVDataSet(__DIR__ . '/Fixtures/userTsConfigTestFixture.csv');
+        $userRow = $this->getBackendUserRecordFromDatabase(5);
+        $backendUser = GeneralUtility::makeInstance(BackendUserAuthentication::class);
+        $request = (new ServerRequest('https://www.example.com/', null, 'php://input', [], ['HTTPS' => 'ON']));
+        $session = $backendUser->createUserSession($userRow);
+        $request = $request->withCookieParams(['be_typo_user' => $session->getJwt()]);
+        $request = $request->withAttribute('normalizedParams', NormalizedParams::createFromRequest($request));
+        $GLOBALS['TYPO3_REQUEST'] = $request;
+        $backendUser = $this->authenticateBackendUser($backendUser, $request);
+        /** @var UserTsConfigFactory $subject */
+        $subject = $this->get(UserTsConfigFactory::class);
+        $userTsConfig = $subject->create($backendUser);
+        self::assertSame('on', $userTsConfig->getUserTsConfigArray()['isHttps']);
+    }
+}
diff --git a/typo3/sysext/core/Tests/Functional/TypoScript/Parser/TypoScriptParserTest.php b/typo3/sysext/core/Tests/FunctionalDeprecated/TypoScript/Parser/TypoScriptParserTest.php
similarity index 96%
rename from typo3/sysext/core/Tests/Functional/TypoScript/Parser/TypoScriptParserTest.php
rename to typo3/sysext/core/Tests/FunctionalDeprecated/TypoScript/Parser/TypoScriptParserTest.php
index bea10144744aaaceb9e60dc78e2a4d5e85a6b50b..b82e7752b31b80c820f335d0290c42858e983d64 100644
--- a/typo3/sysext/core/Tests/Functional/TypoScript/Parser/TypoScriptParserTest.php
+++ b/typo3/sysext/core/Tests/FunctionalDeprecated/TypoScript/Parser/TypoScriptParserTest.php
@@ -15,7 +15,7 @@ declare(strict_types=1);
  * The TYPO3 project - inspiring people to share!
  */
 
-namespace TYPO3\CMS\Core\Tests\Functional\TypoScript\Parser;
+namespace TYPO3\CMS\Core\Tests\FunctionalDeprecated\TypoScript\Parser;
 
 use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
 use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
diff --git a/typo3/sysext/core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript b/typo3/sysext/core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript
deleted file mode 100644
index b843d96cb232717b2843fdeb67c5d8050c060289..0000000000000000000000000000000000000000
--- a/typo3/sysext/core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript
+++ /dev/null
@@ -1 +0,0 @@
-test.Core.TypoScript = 1
\ No newline at end of file
diff --git a/typo3/sysext/core/Tests/Unit/TypoScript/Fixtures/recursive_includes_setup.typoscript b/typo3/sysext/core/Tests/Unit/TypoScript/Fixtures/recursive_includes_setup.typoscript
deleted file mode 100644
index 4c5a49bd4f2dd806c228de26773fd6f64e24c154..0000000000000000000000000000000000000000
--- a/typo3/sysext/core/Tests/Unit/TypoScript/Fixtures/recursive_includes_setup.typoscript
+++ /dev/null
@@ -1 +0,0 @@
-@import 'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript'
diff --git a/typo3/sysext/core/Tests/Unit/Configuration/Loader/Fixtures/included.typoscript b/typo3/sysext/core/Tests/UnitDeprecated/Configuration/Loader/Fixtures/included.typoscript
similarity index 100%
rename from typo3/sysext/core/Tests/Unit/Configuration/Loader/Fixtures/included.typoscript
rename to typo3/sysext/core/Tests/UnitDeprecated/Configuration/Loader/Fixtures/included.typoscript
diff --git a/typo3/sysext/core/Tests/Unit/Configuration/Loader/PageTsConfigLoaderTest.php b/typo3/sysext/core/Tests/UnitDeprecated/Configuration/Loader/PageTsConfigLoaderTest.php
similarity index 77%
rename from typo3/sysext/core/Tests/Unit/Configuration/Loader/PageTsConfigLoaderTest.php
rename to typo3/sysext/core/Tests/UnitDeprecated/Configuration/Loader/PageTsConfigLoaderTest.php
index 8dce931a326e4c8f14c83d20c0c91374625f0d0f..39fc0f64fb92f4e1317515bd2f6083e28b18564a 100644
--- a/typo3/sysext/core/Tests/Unit/Configuration/Loader/PageTsConfigLoaderTest.php
+++ b/typo3/sysext/core/Tests/UnitDeprecated/Configuration/Loader/PageTsConfigLoaderTest.php
@@ -15,11 +15,12 @@ declare(strict_types=1);
  * The TYPO3 project - inspiring people to share!
  */
 
-namespace TYPO3\CMS\Core\Tests\Unit\Configuration\Loader;
+namespace TYPO3\CMS\Core\Tests\UnitDeprecated\Configuration\Loader;
 
 use Psr\EventDispatcher\EventDispatcherInterface;
 use TYPO3\CMS\Core\Configuration\Event\ModifyLoadedPageTsConfigEvent;
 use TYPO3\CMS\Core\Configuration\Loader\PageTsConfigLoader;
+use TYPO3\CMS\Core\EventDispatcher\NoopEventDispatcher;
 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
 
 class PageTsConfigLoaderTest extends UnitTestCase
@@ -69,17 +70,15 @@ class PageTsConfigLoaderTest extends UnitTestCase
     public function loadExternalInclusionsCorrectlyAndKeepLoadingOrder(): void
     {
         $expected = [
+            'global' => '',
             'default' => $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig'],
             'page_13_includes_0' => 'Show_me = more
 ',
             'page_13' => 'waiting for = love',
             'page_27' => '',
         ];
-        $rootLine = [['uid' => 13, 'TSconfig' => 'waiting for = love', 'tsconfig_includes' => 'EXT:core/Tests/Unit/Configuration/Loader/Fixtures/included.typoscript'], ['uid' => 27, 'TSconfig' => '']];
-        $eventDispatcherMock = $this->createMock(EventDispatcherInterface::class);
-        $event = new ModifyLoadedPageTsConfigEvent($expected, $rootLine);
-        $eventDispatcherMock->method('dispatch')->with(self::isInstanceOf(ModifyLoadedPageTsConfigEvent::class))->willReturn($event);
-        $subject = new PageTsConfigLoader($eventDispatcherMock);
+        $rootLine = [['uid' => 13, 'TSconfig' => 'waiting for = love', 'tsconfig_includes' => 'EXT:core/Tests/UnitDeprecated/Configuration/Loader/Fixtures/included.typoscript'], ['uid' => 27, 'TSconfig' => '']];
+        $subject = new PageTsConfigLoader(new NoopEventDispatcher());
         $result = $subject->collect($rootLine);
         self::assertSame($expected, $result);
     }
@@ -90,16 +89,14 @@ class PageTsConfigLoaderTest extends UnitTestCase
     public function invalidExternalFileIsNotLoaded(): void
     {
         $expected = [
+            'global' => '',
             'default' => $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig'],
             'page_13' => 'waiting for = love',
             'page_27' => '',
         ];
         $expectedString = implode("\n[GLOBAL]\n", $expected);
-        $rootLine = [['uid' => 13, 'TSconfig' => 'waiting for = love', 'tsconfig_includes' => 'EXT:core/Tests/Unit/Configuration/Loader/Fixtures/me_does_not_exist.typoscript'], ['uid' => 27, 'TSconfig' => '']];
-        $eventDispatcherMock = $this->createMock(EventDispatcherInterface::class);
-        $event = new ModifyLoadedPageTsConfigEvent($expected, $rootLine);
-        $eventDispatcherMock->method('dispatch')->with(self::isInstanceOf(ModifyLoadedPageTsConfigEvent::class))->willReturn($event);
-        $subject = new PageTsConfigLoader($eventDispatcherMock);
+        $rootLine = [['uid' => 13, 'TSconfig' => 'waiting for = love', 'tsconfig_includes' => 'EXT:core/Tests/UnitDeprecated/Configuration/Loader/Fixtures/me_does_not_exist.typoscript'], ['uid' => 27, 'TSconfig' => '']];
+        $subject = new PageTsConfigLoader(new NoopEventDispatcher());
         $result = $subject->collect($rootLine);
         self::assertSame($expected, $result);
 
diff --git a/typo3/sysext/core/Tests/Unit/Configuration/Parser/PageTsConfigParserTest.php b/typo3/sysext/core/Tests/UnitDeprecated/Configuration/Parser/PageTsConfigParserTest.php
similarity index 98%
rename from typo3/sysext/core/Tests/Unit/Configuration/Parser/PageTsConfigParserTest.php
rename to typo3/sysext/core/Tests/UnitDeprecated/Configuration/Parser/PageTsConfigParserTest.php
index 2311135bf2dd37b14e283f53256751a0b4f7d097..c97269b9f2bf2f5cc2d1f109771eddc6d33d1d3b 100644
--- a/typo3/sysext/core/Tests/Unit/Configuration/Parser/PageTsConfigParserTest.php
+++ b/typo3/sysext/core/Tests/UnitDeprecated/Configuration/Parser/PageTsConfigParserTest.php
@@ -15,7 +15,7 @@ declare(strict_types=1);
  * The TYPO3 project - inspiring people to share!
  */
 
-namespace TYPO3\CMS\Core\Tests\Unit\Configuration\Parser;
+namespace TYPO3\CMS\Core\Tests\UnitDeprecated\Configuration\Parser;
 
 use Psr\Log\NullLogger;
 use TYPO3\CMS\Core\Cache\Backend\TransientMemoryBackend;
diff --git a/typo3/sysext/core/Tests/Unit/TypoScript/Fixtures/badfilename.php b/typo3/sysext/core/Tests/UnitDeprecated/TypoScript/Fixtures/badfilename.php
similarity index 100%
rename from typo3/sysext/core/Tests/Unit/TypoScript/Fixtures/badfilename.php
rename to typo3/sysext/core/Tests/UnitDeprecated/TypoScript/Fixtures/badfilename.php
diff --git a/typo3/sysext/core/Tests/UnitDeprecated/TypoScript/Fixtures/recursive_includes_setup.typoscript b/typo3/sysext/core/Tests/UnitDeprecated/TypoScript/Fixtures/recursive_includes_setup.typoscript
new file mode 100644
index 0000000000000000000000000000000000000000..6a355fc02c88b3b726fe868a1332989e262da904
--- /dev/null
+++ b/typo3/sysext/core/Tests/UnitDeprecated/TypoScript/Fixtures/recursive_includes_setup.typoscript
@@ -0,0 +1 @@
+@import 'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript'
diff --git a/typo3/sysext/core/Tests/Unit/TypoScript/Fixtures/setup.typoscript b/typo3/sysext/core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript
similarity index 100%
rename from typo3/sysext/core/Tests/Unit/TypoScript/Fixtures/setup.typoscript
rename to typo3/sysext/core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript
diff --git a/typo3/sysext/core/Tests/Unit/TypoScript/Parser/TypoScriptParserTest.php b/typo3/sysext/core/Tests/UnitDeprecated/TypoScript/Parser/TypoScriptParserTest.php
similarity index 89%
rename from typo3/sysext/core/Tests/Unit/TypoScript/Parser/TypoScriptParserTest.php
rename to typo3/sysext/core/Tests/UnitDeprecated/TypoScript/Parser/TypoScriptParserTest.php
index 66c2ad83efb66bcbf28c9a43a9d35f25884fefd7..7d1f1e3424c4192147f115a2963c72d547021d37 100644
--- a/typo3/sysext/core/Tests/Unit/TypoScript/Parser/TypoScriptParserTest.php
+++ b/typo3/sysext/core/Tests/UnitDeprecated/TypoScript/Parser/TypoScriptParserTest.php
@@ -15,7 +15,7 @@ declare(strict_types=1);
  * The TYPO3 project - inspiring people to share!
  */
 
-namespace TYPO3\CMS\Core\Tests\Unit\TypoScript\Parser;
+namespace TYPO3\CMS\Core\Tests\UnitDeprecated\TypoScript\Parser;
 
 use TYPO3\CMS\Backend\Configuration\TypoScript\ConditionMatching\ConditionMatcher;
 use TYPO3\CMS\Core\Cache\CacheManager;
@@ -466,34 +466,34 @@ class TypoScriptParserTest extends UnitTestCase
         return [
             'Found include file as single file is imported' => [
                 // Input TypoScript
-                '@import "EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript"'
+                '@import "EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript"'
                 ,
                 // Expected
                 '
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' begin ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' begin ###
 test.Core.TypoScript = 1
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' end ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' end ###
 ',
             ],
             'Found include file is imported' => [
                 // Input TypoScript
                 'bennilove = before
-@import "EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript"
+@import "EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript"
 '
                 ,
                 // Expected
                 '
 bennilove = before
 
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' begin ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' begin ###
 test.Core.TypoScript = 1
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' end ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' end ###
 ',
             ],
             'Not found file is not imported' => [
                 // Input TypoScript
                 'bennilove = before
-@import "EXT:core/Tests/Unit/TypoScript/Fixtures/notfoundfile.typoscript"
+@import "EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/notfoundfile.typoscript"
 '
                 ,
                 // Expected
@@ -501,138 +501,138 @@ test.Core.TypoScript = 1
 bennilove = before
 
 ###
-### ERROR: No file or folder found for importing TypoScript on "EXT:core/Tests/Unit/TypoScript/Fixtures/notfoundfile.typoscript".
+### ERROR: No file or folder found for importing TypoScript on "EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/notfoundfile.typoscript".
 ###
 ',
             ],
             'All files with glob are imported' => [
                 // Input TypoScript
                 'bennilove = before
-@import "EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript*"
+@import "EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript*"
 '
                 ,
                 // Expected
                 '
 bennilove = before
 
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' begin ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' begin ###
 test.Core.TypoScript = 1
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' end ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' end ###
 ',
             ],
             'Specific file with typoscript ending is imported' => [
                 // Input TypoScript
                 'bennilove = before
-@import "EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript"
+@import "EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript"
 '
                 ,
                 // Expected
                 '
 bennilove = before
 
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' begin ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' begin ###
 test.TYPO3Forever.TypoScript = 1
 
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' end ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' end ###
 ',
             ],
             'All files in folder are imported, sorted by name' => [
                 // Input TypoScript
                 'bennilove = before
-@import "EXT:core/Tests/Unit/TypoScript/Fixtures/"
+@import "EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/"
 '
                 ,
                 // Expected
                 '
 bennilove = before
 
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' begin ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' begin ###
 test.Core.TypoScript = 1
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' end ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' end ###
 
 
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/recursive_includes_setup.typoscript\' begin ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/recursive_includes_setup.typoscript\' begin ###
 
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' begin ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' begin ###
 test.TYPO3Forever.TypoScript = 1
 
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' end ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' end ###
 
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/recursive_includes_setup.typoscript\' end ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/recursive_includes_setup.typoscript\' end ###
 
 
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' begin ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' begin ###
 test.TYPO3Forever.TypoScript = 1
 
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' end ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' end ###
 ',
             ],
             'All files ending with typoscript in folder are imported' => [
                 // Input TypoScript
                 'bennilove = before
-@import "EXT:core/Tests/Unit/TypoScript/Fixtures/*typoscript"
+@import "EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/*typoscript"
 '
                 ,
                 // Expected
                 '
 bennilove = before
 
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' begin ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' begin ###
 test.Core.TypoScript = 1
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' end ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' end ###
 
 
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/recursive_includes_setup.typoscript\' begin ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/recursive_includes_setup.typoscript\' begin ###
 
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' begin ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' begin ###
 test.TYPO3Forever.TypoScript = 1
 
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' end ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' end ###
 
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/recursive_includes_setup.typoscript\' end ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/recursive_includes_setup.typoscript\' end ###
 
 
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' begin ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' begin ###
 test.TYPO3Forever.TypoScript = 1
 
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' end ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' end ###
 ',
             ],
             'All typoscript files in folder are imported' => [
                 // Input TypoScript
                 'bennilove = before
-@import "EXT:core/Tests/Unit/TypoScript/Fixtures/*.typoscript"
+@import "EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/*.typoscript"
 '
                 ,
                 // Expected
                 '
 bennilove = before
 
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' begin ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' begin ###
 test.Core.TypoScript = 1
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' end ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' end ###
 
 
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/recursive_includes_setup.typoscript\' begin ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/recursive_includes_setup.typoscript\' begin ###
 
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' begin ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' begin ###
 test.TYPO3Forever.TypoScript = 1
 
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' end ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' end ###
 
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/recursive_includes_setup.typoscript\' end ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/recursive_includes_setup.typoscript\' end ###
 
 
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' begin ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' begin ###
 test.TYPO3Forever.TypoScript = 1
 
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' end ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' end ###
 ',
             ],
             'All typoscript files in folder with glob are not imported due to recursion level=0' => [
                 // Input TypoScript
                 'bennilove = before
-@import "EXT:core/Tests/Unit/**/*.typoscript"
+@import "EXT:core/Tests/UnitDeprecated/**/*.typoscript"
 '
                 ,
                 // Expected
@@ -640,24 +640,24 @@ test.TYPO3Forever.TypoScript = 1
 bennilove = before
 
 ###
-### ERROR: No file or folder found for importing TypoScript on "EXT:core/Tests/Unit/**/*.typoscript".
+### ERROR: No file or folder found for importing TypoScript on "EXT:core/Tests/UnitDeprecated/**/*.typoscript".
 ###
 ',
             ],
             'TypoScript file ending is automatically added' => [
                 // Input TypoScript
                 'bennilove = before
-@import "EXT:core/Tests/Unit/TypoScript/Fixtures/setup"
+@import "EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup"
 '
                 ,
                 // Expected
                 '
 bennilove = before
 
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' begin ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' begin ###
 test.TYPO3Forever.TypoScript = 1
 
-### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' end ###
+### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' end ###
 ',
             ],
         ];
diff --git a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
index f168a2e72f10d088cac707287ad42d325f30e587..4eaf76043bd9e572d2feac997de8c7881047f80c 100644
--- a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
+++ b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
@@ -2543,9 +2543,14 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      *******************************************/
     /**
      * Returns the pages TSconfig array based on the current ->rootLine
+     *
+     * @deprecated since TYPO3 v12, will be removed in v13. Frontend should typically not depend on Backend TsConfig.
+     *             If really needed, use PageTsConfigFactory, see usage in DatabaseRecordLinkBuilder.
+     *             Remove together with class PageTsConfig.
      */
     public function getPagesTSconfig(): array
     {
+        trigger_error('Method getPagesTSconfig() is deprecated since TYPO3 v12 and will be removed with TYPO3 v13.0.', E_USER_DEPRECATED);
         if (!is_array($this->pagesTSconfig)) {
             $matcher = GeneralUtility::makeInstance(FrontendConditionMatcher::class, $this->context, $this->id, $this->rootLine);
             $this->pagesTSconfig = GeneralUtility::makeInstance(PageTsConfig::class)
diff --git a/typo3/sysext/frontend/Classes/Typolink/DatabaseRecordLinkBuilder.php b/typo3/sysext/frontend/Classes/Typolink/DatabaseRecordLinkBuilder.php
index 96f0e202de1d5c5241aa920aacada296d94e45d7..7130b84d29391c9a3ecf910a26d6046c58d95eb2 100644
--- a/typo3/sysext/frontend/Classes/Typolink/DatabaseRecordLinkBuilder.php
+++ b/typo3/sysext/frontend/Classes/Typolink/DatabaseRecordLinkBuilder.php
@@ -17,9 +17,17 @@ declare(strict_types=1);
 
 namespace TYPO3\CMS\Frontend\Typolink;
 
+use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Core\Cache\CacheManager;
+use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\LinkHandling\TypoLinkCodecService;
+use TYPO3\CMS\Core\Site\Entity\NullSite;
+use TYPO3\CMS\Core\TypoScript\PageTsConfig;
+use TYPO3\CMS\Core\TypoScript\PageTsConfigFactory;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Frontend\Configuration\TypoScript\ConditionMatching\ConditionMatcher as FrontendConditionMatcher;
 use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
+use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 
 /**
  * Builds a TypoLink to a database record
@@ -29,9 +37,9 @@ class DatabaseRecordLinkBuilder extends AbstractTypolinkBuilder
     public function build(array &$linkDetails, string $linkText, string $target, array $conf): LinkResultInterface
     {
         $tsfe = $this->getTypoScriptFrontendController();
-        $pageTsConfig = $tsfe->getPagesTSconfig();
-        $configurationKey = $linkDetails['identifier'] . '.';
         $request = $this->contentObjectRenderer->getRequest();
+        $pageTsConfig = $this->getPageTsConfig($tsfe, $request);
+        $configurationKey = $linkDetails['identifier'] . '.';
         $typoScriptArray = $request->getAttribute('frontend.typoscript')->getSetupArray();
         $configuration = $typoScriptArray['config.']['recordLinks.'] ?? [];
         $linkHandlerConfiguration = $pageTsConfig['TCEMAIN.']['linkHandler.'] ?? [];
@@ -98,4 +106,27 @@ class DatabaseRecordLinkBuilder extends AbstractTypolinkBuilder
         $localContentObjectRenderer->parameters = $this->contentObjectRenderer->parameters;
         return $localContentObjectRenderer->createLink($linkText, $typoScriptConfiguration);
     }
+
+    /**
+     * Helper method to calculate pageTsConfig in frontend scope, we can't use BackendUtility::getPagesTSconfig() here.
+     */
+    protected function getPageTsConfig(TypoScriptFrontendController $tsfe, ServerRequestInterface $request): array
+    {
+        $id = $tsfe->id;
+        $runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime');
+        $pageTsConfig = $runtimeCache->get('pageTsConfig-' . $id);
+        if ($pageTsConfig instanceof PageTsConfig) {
+            return $pageTsConfig->getPageTsConfigArray();
+        }
+        $conditionMatcher = GeneralUtility::makeInstance(FrontendConditionMatcher::class, GeneralUtility::makeInstance(Context::class), $tsfe->id, $tsfe->rootLine);
+        $site = $request->getAttribute('site') ?? new NullSite();
+        $pageTsConfigFactory = GeneralUtility::makeInstance(PageTsConfigFactory::class);
+        $pageTsConfig = $pageTsConfigFactory->create(
+            $tsfe->rootLine,
+            $site,
+            $conditionMatcher
+        );
+        $runtimeCache->set('pageTsConfig-' . $id, $pageTsConfig);
+        return $pageTsConfig->getPageTsConfigArray();
+    }
 }
diff --git a/typo3/sysext/frontend/Tests/Unit/Typolink/DatabaseRecordLinkBuilderTest.php b/typo3/sysext/frontend/Tests/Unit/Typolink/DatabaseRecordLinkBuilderTest.php
index 12d0ef785ffe07d99a17bb5417d0976f71678d39..c1806bea8c5686b550b697807bfb584e2f54c9c3 100644
--- a/typo3/sysext/frontend/Tests/Unit/Typolink/DatabaseRecordLinkBuilderTest.php
+++ b/typo3/sysext/frontend/Tests/Unit/Typolink/DatabaseRecordLinkBuilderTest.php
@@ -176,10 +176,9 @@ class DatabaseRecordLinkBuilderTest extends UnitTestCase
         $contentObjectRendererMock->expects(self::once())->method('start');
         $contentObjectRendererMock->expects(self::once())->method('createLink');
 
-        $frontendControllerMock->method('getPagesTSconfig')->willReturn($pageTsConfig);
-
         // Act
-        $databaseRecordLinkBuilder = new DatabaseRecordLinkBuilder($contentObjectRendererMock, $frontendControllerMock);
+        $databaseRecordLinkBuilder = $this->getAccessibleMock(DatabaseRecordLinkBuilder::class, ['getPageTsConfig'], [$contentObjectRendererMock, $frontendControllerMock]);
+        $databaseRecordLinkBuilder->method('getPageTsConfig')->willReturn($pageTsConfig);
         try {
             $databaseRecordLinkBuilder->build($extractedLinkDetails, $linkText, $target, $confFromDb);
         } catch (UnableToLinkException) {
diff --git a/typo3/sysext/info/Classes/Controller/InfoPageTyposcriptConfigController.php b/typo3/sysext/info/Classes/Controller/InfoPageTyposcriptConfigController.php
index a471eef824d7f1ac1e65d539716174570b5452b0..0acc506c627b35a04acd5b8da857a8ddb1731fb6 100644
--- a/typo3/sysext/info/Classes/Controller/InfoPageTyposcriptConfigController.php
+++ b/typo3/sysext/info/Classes/Controller/InfoPageTyposcriptConfigController.php
@@ -20,12 +20,14 @@ namespace TYPO3\CMS\Info\Controller;
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
-use TYPO3\CMS\Core\Configuration\Loader\PageTsConfigLoader;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction;
+use TYPO3\CMS\Core\TypoScript\IncludeTree\TsConfigTreeBuilder;
+use TYPO3\CMS\Core\TypoScript\Tokenizer\Line\LineStream;
+use TYPO3\CMS\Core\TypoScript\Tokenizer\LosslessTokenizer;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
@@ -59,23 +61,30 @@ class InfoPageTyposcriptConfigController extends InfoModuleController
 
         if ((int)$moduleData->get('tsconf_parts') === 99) {
             $rootLine = BackendUtility::BEgetRootLine($this->id, '', true);
-            /** @var array<string, string> $TSparts */
-            $TSparts = GeneralUtility::makeInstance(PageTsConfigLoader::class)->collect($rootLine);
+
+            $tsConfigTreeBuilder = GeneralUtility::makeInstance(TsConfigTreeBuilder::class);
+            $pageTsConfigTree = $tsConfigTreeBuilder->getPagesTsConfigTree($rootLine, new LosslessTokenizer());
+            // @todo: This is a bit dusty. It would be better to render the full tree similar to
+            //        tstemplate Template Analyzer. For now, we simply create an array with the
+            //        to-string'ed content and render this.
+            $TSparts = [];
+            foreach ($pageTsConfigTree->getNextChild() as $child) {
+                $lineStream = $child->getLineStream();
+                if ($lineStream instanceof LineStream) {
+                    $TSparts[$child->getName()] = (string)$lineStream;
+                }
+            }
+
             $lines = [];
             $pUids = [];
 
             foreach ($TSparts as $key => $value) {
-                $line = [];
-                if ($key === 'global') {
-                    $title = $this->getLanguageService()->sL('LLL:EXT:info/Resources/Private/Language/InfoPageTsConfig.xlf:editTSconfig_global');
-                    $line['title'] = $title;
-                } elseif ($key === 'default') {
-                    $title = $this->getLanguageService()->sL('LLL:EXT:info/Resources/Private/Language/InfoPageTsConfig.xlf:editTSconfig_default');
-                    $line['title'] = $title;
-                } else {
-                    // Remove the "page_" prefix
-                    [, $pageId] = explode('_', $key, 3);
-                    $pageId = (int)$pageId;
+                $title = $key;
+                $line = [
+                    'title' => $key,
+                ];
+                if (str_starts_with($key, 'pagesTsConfig-page-')) {
+                    $pageId = (int)explode('-', $key)[2];
                     $pUids[] = $pageId;
                     $row = BackendUtility::getRecordWSOL('pages', $pageId);
                     $icon = $this->iconFactory->getIconForRecord('pages', $row, Icon::SIZE_SMALL);
diff --git a/typo3/sysext/info/Resources/Private/Language/InfoPageTsConfig.xlf b/typo3/sysext/info/Resources/Private/Language/InfoPageTsConfig.xlf
index 062036cda980697dc4b7e9b433617f4269e2cf85..cc503d491cd94c281aa6b08da5c8dd52f21996d8 100644
--- a/typo3/sysext/info/Resources/Private/Language/InfoPageTsConfig.xlf
+++ b/typo3/sysext/info/Resources/Private/Language/InfoPageTsConfig.xlf
@@ -36,12 +36,6 @@
 			<trans-unit id="editTSconfig_all" resname="editTSconfig_all">
 				<source>Edit TSconfig for all shown pages</source>
 			</trans-unit>
-			<trans-unit id="editTSconfig_global" resname="editTSconfig_global">
-				<source>Global Configuration</source>
-			</trans-unit>
-			<trans-unit id="editTSconfig_default" resname="editTSconfig_default">
-				<source>Default Configuration from TYPO3_CONF_VARS</source>
-			</trans-unit>
 			<trans-unit id="mod_pagetsconfig" resname="mod_pagetsconfig">
 				<source>Page TSconfig</source>
 			</trans-unit>
diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php
index 64d85dc2d75177da0c5ddf4e1993a633a02e48fd..d00c427452fba3f0d895d4c19e64bd17a767b213 100644
--- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php
+++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php
@@ -2208,4 +2208,29 @@ return [
             'Breaking-97816-NewTypoScriptParserInFrontend.rst',
         ],
     ],
+    'TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser' => [
+        'restFiles' => [
+            'Deprecation-99120-DeprecateOldTypoScriptParser.rst',
+        ],
+    ],
+    'TYPO3\CMS\Core\Configuration\Loader\PageTsConfigLoader' => [
+        'restFiles' => [
+            'Deprecation-99120-DeprecateOldTypoScriptParser.rst',
+        ],
+    ],
+    'TYPO3\CMS\Core\Configuration\PageTsConfig' => [
+        'restFiles' => [
+            'Deprecation-99120-DeprecateOldTypoScriptParser.rst',
+        ],
+    ],
+    'TYPO3\CMS\Core\Configuration\Parser\PageTsConfigParser' => [
+        'restFiles' => [
+            'Deprecation-99120-DeprecateOldTypoScriptParser.rst',
+        ],
+    ],
+    'TYPO3\CMS\Core\Configuration\Event\ModifyLoadedPageTsConfigEvent' => [
+        'restFiles' => [
+            'Deprecation-99120-DeprecateOldTypoScriptParser.rst',
+        ],
+    ],
 ];
diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php
index ae53a85365484914a45e46fc536a2a8035193aa4..d3ca927f81037bcd3330e9dcc92849e3f8ee8419 100644
--- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php
+++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php
@@ -5407,4 +5407,11 @@ return [
             'Deprecation-99170-ConfigbaseURLAndBaseTagFunctionality.rst',
         ],
     ],
+    'TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->getPagesTSconfig' => [
+        'numberOfMandatoryArguments' => 0,
+        'maximumNumberOfArguments' => 0,
+        'restFiles' => [
+            'Deprecation-99120-DeprecateOldTypoScriptParser.rst',
+        ],
+    ],
 ];
diff --git a/typo3/sysext/linkvalidator/Classes/Task/ValidatorTask.php b/typo3/sysext/linkvalidator/Classes/Task/ValidatorTask.php
index 65457d9e691eec8d97d8d6a34824800d73f8f99f..041ca74de2cdffa450d11ab83e1590eb31f8709d 100644
--- a/typo3/sysext/linkvalidator/Classes/Task/ValidatorTask.php
+++ b/typo3/sysext/linkvalidator/Classes/Task/ValidatorTask.php
@@ -23,7 +23,8 @@ use Symfony\Component\Mime\Address;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Mail\FluidEmail;
 use TYPO3\CMS\Core\Mail\MailerInterface;
-use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
+use TYPO3\CMS\Core\TypoScript\AST\AstBuilder;
+use TYPO3\CMS\Core\TypoScript\TypoScriptStringFactory;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MailUtility;
@@ -326,19 +327,12 @@ class ValidatorTask extends AbstractTask
      */
     protected function loadModTSconfig(): void
     {
-        $parseObj = GeneralUtility::makeInstance(TypoScriptParser::class);
-        $parseObj->parse($this->configuration);
-        if (!empty($parseObj->errors)) {
-            $parseErrorMessage = $this->getLanguageService()->sL($this->languageFile . ':tasks.error.invalidTSconfig') . '<br />';
-            foreach ($parseObj->errors as $errorInfo) {
-                $parseErrorMessage .= $errorInfo[0] . '<br />';
-            }
-            throw new \Exception($parseErrorMessage, 1295476989);
-        }
         $modTs = BackendUtility::getPagesTSconfig($this->page)['mod.']['linkvalidator.'] ?? [];
-        $overrideTs = $parseObj->setup['mod.']['linkvalidator.'] ?? [];
-        if (is_array($overrideTs) && $overrideTs !== []) {
-            ArrayUtility::mergeRecursiveWithOverrule($modTs, $overrideTs);
+
+        if (!empty($this->configuration)) {
+            $typoScriptStringFactory = GeneralUtility::makeInstance(TypoScriptStringFactory::class);
+            $overrideTs = $typoScriptStringFactory->parseFromString($this->configuration, GeneralUtility::makeInstance(AstBuilder::class));
+            ArrayUtility::mergeRecursiveWithOverrule($modTs, $overrideTs->toArray()['mod.']['linkvalidator.'] ?? []);
         }
 
         if (empty($modTs['mail.']['fromemail'])) {
diff --git a/typo3/sysext/linkvalidator/Resources/Private/Language/locallang.xlf b/typo3/sysext/linkvalidator/Resources/Private/Language/locallang.xlf
index f71788aaeed22fbc2bfd13f6048f46d663589e94..7e90ccd73f0d20b95aa6b48ca074fcc6ee44fa09 100644
--- a/typo3/sysext/linkvalidator/Resources/Private/Language/locallang.xlf
+++ b/typo3/sysext/linkvalidator/Resources/Private/Language/locallang.xlf
@@ -58,9 +58,6 @@
 			<trans-unit id="tasks.error.invalidFromEmail" resname="tasks.error.invalidFromEmail">
 				<source>Invalid format of the email address in the from header</source>
 			</trans-unit>
-			<trans-unit id="tasks.error.invalidTSconfig" resname="tasks.error.invalidTSconfig">
-				<source>Invalid TSconfig in the task configuration!</source>
-			</trans-unit>
 			<trans-unit id="tasks.error.invalidPageUid" resname="tasks.error.invalidPageUid">
 				<source>The given page id %d is invalid!</source>
 			</trans-unit>