diff --git a/typo3/sysext/backend/Classes/Configuration/SiteTcaConfiguration.php b/typo3/sysext/backend/Classes/Configuration/SiteTcaConfiguration.php
new file mode 100644
index 0000000000000000000000000000000000000000..9347d2907d4bf894560d8632fdd525a3a2f90f96
--- /dev/null
+++ b/typo3/sysext/backend/Classes/Configuration/SiteTcaConfiguration.php
@@ -0,0 +1,72 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Backend\Configuration;
+
+/*
+ * 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!
+ */
+
+use Symfony\Component\Finder\Finder;
+use TYPO3\CMS\Core\Package\PackageManager;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Helper class for the backend "Sites" module
+ *
+ * Load Site configuration TCA from ext:*Configuration/SiteConfigurationTCA
+ * and ext:*Configuration/SiteConfigurationTCA/Overrides
+ */
+class SiteTcaConfiguration
+{
+    /**
+     * Returns a "fake TCA" array that is syntactically identical to
+     * "normal" TCA, and just isn't available as $GLOBALS['TCA'].
+     *
+     * @return array
+     */
+    public function getTca(): array
+    {
+        // To allow casual ExtensionManagementUtility methods that works on $GLOBALS['TCA']
+        // to change our fake TCA, just kick original TCA, and reset to original at the end.
+        $originalTca = $GLOBALS['TCA'];
+        $GLOBALS['TCA'] = [];
+        $activePackages = GeneralUtility::makeInstance(PackageManager::class)->getActivePackages();
+        // First load "full table" files from Configuration/SiteConfigurationTCA
+        $finder = new Finder();
+        foreach ($activePackages as $package) {
+            try {
+                $finder->files()->depth(0)->name('*.php')->in($package->getPackagePath() . 'Configuration/SiteConfigurationTCA');
+            } catch (\InvalidArgumentException $e) {
+                // No such directory in this package
+                continue;
+            }
+            foreach ($finder as $fileInfo) {
+                $GLOBALS['TCA'][substr($fileInfo->getBasename(), 0, -4)] = require $fileInfo->getPathname();
+            }
+        }
+        // Execute override files from Configuration/TCA/Overrides
+        foreach ($activePackages as $package) {
+            try {
+                $finder->files()->depth(0)->name('*.php')->in($package->getPackagePath() . 'Configuration/SiteConfigurationTCA/Overrides');
+            } catch (\InvalidArgumentException $e) {
+                // No such directory in this package
+                continue;
+            }
+            foreach ($finder as $fileInfo) {
+                require $fileInfo->getPathname();
+            }
+        }
+        $result = $GLOBALS['TCA'];
+        $GLOBALS['TCA'] = $originalTca;
+        return $result;
+    }
+}
diff --git a/typo3/sysext/backend/Classes/Controller/SiteConfigurationController.php b/typo3/sysext/backend/Classes/Controller/SiteConfigurationController.php
new file mode 100644
index 0000000000000000000000000000000000000000..de8ea402b2536282e06315952cb90d2627a12f01
--- /dev/null
+++ b/typo3/sysext/backend/Classes/Controller/SiteConfigurationController.php
@@ -0,0 +1,645 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Backend\Controller;
+
+/*
+ * 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!
+ */
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Backend\Configuration\SiteTcaConfiguration;
+use TYPO3\CMS\Backend\Exception\SiteValidationErrorException;
+use TYPO3\CMS\Backend\Form\FormDataCompiler;
+use TYPO3\CMS\Backend\Form\FormDataGroup\SiteConfigurationDataGroup;
+use TYPO3\CMS\Backend\Form\FormResultCompiler;
+use TYPO3\CMS\Backend\Form\NodeFactory;
+use TYPO3\CMS\Backend\Routing\UriBuilder;
+use TYPO3\CMS\Backend\Template\Components\ButtonBar;
+use TYPO3\CMS\Backend\Template\ModuleTemplate;
+use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Configuration\SiteConfiguration;
+use TYPO3\CMS\Core\Core\Environment;
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Exception\SiteNotFoundException;
+use TYPO3\CMS\Core\Http\HtmlResponse;
+use TYPO3\CMS\Core\Http\RedirectResponse;
+use TYPO3\CMS\Core\Imaging\Icon;
+use TYPO3\CMS\Core\Localization\LanguageService;
+use TYPO3\CMS\Core\Messaging\FlashMessage;
+use TYPO3\CMS\Core\Messaging\FlashMessageService;
+use TYPO3\CMS\Core\Site\Entity\Site;
+use TYPO3\CMS\Core\Site\SiteFinder;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Fluid\View\StandaloneView;
+use TYPO3Fluid\Fluid\View\ViewInterface;
+
+/**
+ * Backend controller: The "Site management" -> "Sites" module
+ *
+ * List all site root pages, CRUD site configuration.
+ */
+class SiteConfigurationController
+{
+    /**
+     * @var ModuleTemplate
+     */
+    protected $moduleTemplate;
+
+    /**
+     * @var ViewInterface
+     */
+    protected $view;
+
+    /**
+     * @var SiteFinder
+     */
+    protected $siteFinder;
+
+    /**
+     * Default constructor
+     */
+    public function __construct()
+    {
+        $this->siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
+        $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
+    }
+
+    /**
+     * Main entry method: Dispatch to other actions - those method names that end with "Action".
+     *
+     * @param ServerRequestInterface $request the current request
+     * @return ResponseInterface the response with the content
+     */
+    public function handleRequest(ServerRequestInterface $request): ResponseInterface
+    {
+        $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
+        $action = $request->getQueryParams()['action'] ?? $request->getParsedBody()['action'] ?? 'overview';
+        $this->initializeView($action);
+        $result = call_user_func_array([$this, $action . 'Action'], [$request]);
+        if ($result instanceof ResponseInterface) {
+            return $result;
+        }
+        $this->moduleTemplate->setContent($this->view->render());
+        return new HtmlResponse($this->moduleTemplate->renderContent());
+    }
+
+    /**
+     * List pages that have 'is_siteroot' flag set - those that have the globe icon in page tree.
+     * Link to Add / Edit / Delete for each.
+     */
+    protected function overviewAction(): void
+    {
+        $this->configureOverViewDocHeader();
+        $allSites = $this->siteFinder->getAllSites();
+        $pages = $this->getAllSitePages();
+        foreach ($allSites as $identifier => $site) {
+            $rootPageId = $site->getRootPageId();
+            if (isset($pages[$rootPageId])) {
+                $pages[$rootPageId]['siteIdentifier'] = $identifier;
+                $pages[$rootPageId]['siteConfiguration'] = $site;
+            }
+        }
+        $this->view->assign('pages', $pages);
+    }
+
+    /**
+     * Shows a form to create a new site configuration, or edit an existing one.
+     *
+     * @param ServerRequestInterface $request
+     * @throws \RuntimeException
+     */
+    protected function editAction(ServerRequestInterface $request): void
+    {
+        $this->configureEditViewDocHeader();
+
+        // Put sys_site and friends TCA into global TCA
+        // @todo: We might be able to get rid of that later
+        $GLOBALS['TCA'] = array_merge($GLOBALS['TCA'], GeneralUtility::makeInstance(SiteTcaConfiguration::class)->getTca());
+
+        $siteIdentifier = $request->getQueryParams()['site'] ?? null;
+        $pageUid = (int)($request->getQueryParams()['pageUid'] ?? 0);
+
+        if (empty($siteIdentifier) && empty($pageUid)) {
+            throw new \RuntimeException('Either site identifier to edit a config or page uid to add new config must be set', 1521561148);
+        }
+        $isNewConfig = empty($siteIdentifier);
+
+        $defaultValues = [];
+        if ($isNewConfig) {
+            $defaultValues['sys_site']['rootPageId'] = $pageUid;
+        }
+
+        $allSites = $this->siteFinder->getAllSites();
+        if (!$isNewConfig && !isset($allSites[$siteIdentifier])) {
+            throw new \RuntimeException('Existing config for site ' . $siteIdentifier . ' not found', 1521561226);
+        }
+
+        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
+        $returnUrl = $uriBuilder->buildUriFromRoute('site_configuration');
+
+        $formDataGroup = GeneralUtility::makeInstance(SiteConfigurationDataGroup::class);
+        $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
+        $formDataCompilerInput = [
+            'tableName' => 'sys_site',
+            'vanillaUid' => $isNewConfig ? $pageUid : $allSites[$siteIdentifier]->getRootPageId(),
+            'command' => $isNewConfig ? 'new' : 'edit',
+            'returnUrl' => (string)$returnUrl,
+            'customData' => [
+                'siteIdentifier' => $isNewConfig ? '' : $siteIdentifier,
+            ],
+            'defaultValues' => $defaultValues,
+        ];
+        $formData = $formDataCompiler->compile($formDataCompilerInput);
+        $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
+        $formData['renderType'] = 'outerWrapContainer';
+        $formResult = $nodeFactory->create($formData)->render();
+        // Needed to be set for 'onChange="reload"' and reload on type change to work
+        $formResult['doSaveFieldName'] = 'doSave';
+        $formResultCompiler = GeneralUtility::makeInstance(FormResultCompiler::class);
+        $formResultCompiler->mergeResult($formResult);
+        $formResultCompiler->addCssFiles();
+        // Always add rootPageId as additional field to have a reference for new records
+        $this->view->assign('rootPageId', $isNewConfig ? $pageUid : $allSites[$siteIdentifier]->getRootPageId());
+        $this->view->assign('returnUrl', $returnUrl);
+        $this->view->assign('formEngineHtml', $formResult['html']);
+        $this->view->assign('formEngineFooter', $formResultCompiler->printNeededJSFunctions());
+    }
+
+    /**
+     * Save incoming data from editAction and redirect to overview or edit
+     *
+     * @param ServerRequestInterface $request
+     * @return ResponseInterface
+     * @throws \RuntimeException
+     */
+    protected function saveAction(ServerRequestInterface $request): ResponseInterface
+    {
+        // Put sys_site and friends TCA into global TCA
+        // @todo: We might be able to get rid of that later
+        $GLOBALS['TCA'] = array_merge($GLOBALS['TCA'], GeneralUtility::makeInstance(SiteTcaConfiguration::class)->getTca());
+
+        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
+        $siteTca = GeneralUtility::makeInstance(SiteTcaConfiguration::class)->getTca();
+
+        $overviewRoute = $uriBuilder->buildUriFromRoute('site_configuration', ['action' => 'overview']);
+        $parsedBody = $request->getParsedBody();
+        if (isset($parsedBody['closeDoc']) && (int)$parsedBody['closeDoc'] === 1) {
+            // Closing means no save, just redirect to overview
+            return new RedirectResponse($overviewRoute);
+        }
+        $isSave = $parsedBody['_savedok'] ?? $parsedBody['doSave'] ?? false;
+        $isSaveClose = $parsedBody['_saveandclosedok'] ?? false;
+        if (!$isSave && !$isSaveClose) {
+            throw new \RuntimeException('Either save or save and close', 1520370364);
+        }
+
+        if (!isset($parsedBody['data']['sys_site']) || !is_array($parsedBody['data']['sys_site'])) {
+            throw new \RuntimeException('No sys_site data or sys_site identifier given', 1521030950);
+        }
+
+        $data = $parsedBody['data'];
+        // This can be NEW123 for new records
+        $pageId = (int)key($data['sys_site']);
+        $sysSiteRow = current($data['sys_site']);
+        $siteIdentifier = $sysSiteRow['identifier'] ?? '';
+
+        $isNewConfiguration = false;
+        $currentIdentifier = '';
+        try {
+            $currentSite = $this->siteFinder->getSiteByRootPageId($pageId);
+            $currentSiteConfiguration = $currentSite->getConfiguration();
+            $currentIdentifier = $currentSite->getIdentifier();
+        } catch (SiteNotFoundException $e) {
+            $isNewConfiguration = true;
+            $pageId = (int)$parsedBody['rootPageId'];
+            if (!$pageId > 0) {
+                // Early validation of rootPageId - it must always be given and greater than 0
+                throw new \RuntimeException('No root page id found', 1521719709);
+            }
+        }
+
+        // Validate site identifier and do not store or further process it
+        $siteIdentifier = $this->validateAndProcessIdentifier($isNewConfiguration, $siteIdentifier, $pageId);
+        unset($sysSiteRow['identifier']);
+
+        try {
+            $newSysSiteData = [];
+            // Hard set rootPageId: This is TCA readOnly and not transmitted by FormEngine, but is also the "uid" of the sys_site record
+            $newSysSiteData['site']['rootPageId'] = $pageId;
+            foreach ($sysSiteRow as $fieldName => $fieldValue) {
+                $type = $siteTca['sys_site']['columns'][$fieldName]['config']['type'];
+                if ($type === 'input') {
+                    $fieldValue = $this->validateAndProcessValue('sys_site', $fieldName, $fieldValue);
+                    $newSysSiteData['site'][$fieldName] = $fieldValue;
+                } elseif ($type === 'inline') {
+                    $newSysSiteData['site'][$fieldName] = [];
+                    $childRowIds = GeneralUtility::trimExplode(',', $fieldValue, true);
+                    if (!isset($siteTca['sys_site']['columns'][$fieldName]['config']['foreign_table'])) {
+                        throw new \RuntimeException('No foreign_table found for inline type', 1521555037);
+                    }
+                    $foreignTable = $siteTca['sys_site']['columns'][$fieldName]['config']['foreign_table'];
+                    foreach ($childRowIds as $childRowId) {
+                        $childRowData = [];
+                        if (!isset($data[$foreignTable][$childRowId])) {
+                            if (!empty($currentSiteConfiguration[$fieldName][$childRowId])) {
+                                // A collapsed inline record: Fetch data from existing config
+                                $newSysSiteData['site'][$fieldName][] = $currentSiteConfiguration[$fieldName][$childRowId];
+                                continue;
+                            }
+                            throw new \RuntimeException('No data found for table ' . $foreignTable . ' with id ' . $childRowId, 1521555177);
+                        }
+                        $childRow = $data[$foreignTable][$childRowId];
+                        foreach ($childRow as $childFieldName => $childFieldValue) {
+                            if ($childFieldName === 'pid') {
+                                // pid is added by inline by default, but not relevant for yml storage
+                                continue;
+                            }
+                            $type = $siteTca[$foreignTable]['columns'][$childFieldName]['config']['type'];
+                            if ($type === 'input') {
+                                $childRowData[$childFieldName] = $childFieldValue;
+                            } elseif ($type === 'select') {
+                                $childRowData[$childFieldName] = $childFieldValue;
+                            } else {
+                                throw new \RuntimeException('TCA type ' . $type . ' not implemented in site handling', 1521555340);
+                            }
+                        }
+                        $newSysSiteData['site'][$fieldName][] = $childRowData;
+                    }
+                } elseif ($type === 'select') {
+                    $newSysSiteData['site'][$fieldName] = (int)$fieldValue;
+                } else {
+                    throw new \RuntimeException('TCA type ' . $type . ' not implemented in site handling', 1521032781);
+                }
+            }
+
+            $newSiteConfiguration = $this->validateFullStructure($newSysSiteData);
+
+            // Persist the configuration
+            $siteConfigurationManager = GeneralUtility::makeInstance(SiteConfiguration::class, Environment::getConfigPath() . '/sites');
+            if (!$isNewConfiguration && $currentIdentifier !== $siteIdentifier) {
+                $siteConfigurationManager->rename($currentIdentifier, $siteIdentifier);
+            }
+            $siteConfigurationManager->write($siteIdentifier, $newSiteConfiguration);
+        } catch (SiteValidationErrorException $e) {
+            // Do not store new config if a validation error is thrown, but redirect only to show a generated flash message
+        }
+
+        $saveRoute = $uriBuilder->buildUriFromRoute('site_configuration', ['action' => 'edit', 'site' => $siteIdentifier]);
+        if ($isSaveClose) {
+            return new RedirectResponse($overviewRoute);
+        }
+        return new RedirectResponse($saveRoute);
+    }
+
+    /**
+     * Validation and processing of site identifier
+     *
+     * @param bool $isNew If true, we're dealing with a new record
+     * @param string $identifier Given identifier to validate and process
+     * @param int $rootPageId Page uid this identifier is bound to
+     * @return mixed Verified / modified value
+     */
+    protected function validateAndProcessIdentifier(bool $isNew, string $identifier, int $rootPageId)
+    {
+        $languageService = $this->getLanguageService();
+        // Normal "eval" processing of field first
+        $identifier = $this->validateAndProcessValue('sys_site', 'identifier', $identifier);
+        if ($isNew) {
+            // Verify no other site with this identifier exists. If so, find a new unique name as
+            // identifier and show a flash message the identifier has been adapted
+            try {
+                $this->siteFinder->getSiteByIdentifier($identifier);
+                // Force this identifier to be unique
+                $originalIdentifier = $identifier;
+                $identifier = $identifier . '-' . str_replace('.', '', uniqid((string)mt_rand(), true));
+                $message = sprintf(
+                    $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.identifierRenamed.message'),
+                    $originalIdentifier,
+                    $identifier
+                );
+                $messageTitle = $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.identifierRenamed.title');
+                $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, $messageTitle, FlashMessage::WARNING, true);
+                $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
+                $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
+                $defaultFlashMessageQueue->enqueue($flashMessage);
+            } catch (SiteNotFoundException $e) {
+                // Do nothing, this new identifier is ok
+            }
+        } else {
+            // If this is an existing config, the site for this identifier must have the same rootPageId, otherwise
+            // a user tried to rename a site identifier to a different site that already exists. If so, we do not rename
+            // the site and show a flash message
+            try {
+                $site = $this->siteFinder->getSiteByIdentifier($identifier);
+                if ($site->getRootPageId() !== $rootPageId) {
+                    // Find original value and keep this
+                    $origSite = $this->siteFinder->getSiteByRootPageId($rootPageId);
+                    $originalIdentifier = $identifier;
+                    $identifier = $origSite->getIdentifier();
+                    $message = sprintf(
+                        $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.identifierExists.message'),
+                        $originalIdentifier,
+                        $identifier
+                    );
+                    $messageTitle = $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.identifierExists.title');
+                    $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, $messageTitle, FlashMessage::WARNING, true);
+                    $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
+                    $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
+                    $defaultFlashMessageQueue->enqueue($flashMessage);
+                }
+            } catch (SiteNotFoundException $e) {
+                // User is renaming identifier which does not exist yet. That's ok
+            }
+        }
+        return $identifier;
+    }
+
+    /**
+     * Simple validation and processing method for incoming form field values.
+     *
+     * Note this does not support all TCA "eval" options but only what we really need.
+     *
+     * @param string $tableName Table name
+     * @param string $fieldName Field name
+     * @param mixed $fieldValue Incoming value from FormEngine
+     * @return mixed Verified / modified value
+     * @throws SiteValidationErrorException
+     * @throws \RuntimeException
+     */
+    protected function validateAndProcessValue(string $tableName, string $fieldName, $fieldValue)
+    {
+        $languageService = $this->getLanguageService();
+        $fieldConfig = $GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
+        $handledEvals = [];
+        if (!empty($fieldConfig['eval'])) {
+            $evalArray = GeneralUtility::trimExplode(',', $fieldConfig['eval'], true);
+            // Processing
+            if (in_array('alphanum_x', $evalArray, true)) {
+                $handledEvals[] = 'alphanum_x';
+                $fieldValue = preg_replace('/[^a-zA-Z0-9_-]/', '', $fieldValue);
+            }
+            if (in_array('lower', $evalArray, true)) {
+                $handledEvals[] = 'lower';
+                $fieldValue = mb_strtolower($fieldValue, 'utf-8');
+            }
+            if (in_array('trim', $evalArray, true)) {
+                $handledEvals[] = 'trim';
+                $fieldValue = trim($fieldValue);
+            }
+            if (in_array('int', $evalArray, true)) {
+                $handledEvals[] = 'int';
+                $fieldValue = (int)$fieldValue;
+            }
+            // Validation throws - these should be handled client side already,
+            // eg. 'required' being set and receiving empty, shouldn't happen server side
+            if (in_array('required', $evalArray, true)) {
+                $handledEvals[] = 'required';
+                if (empty($fieldValue)) {
+                    $message = sprintf(
+                        $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.required.message'),
+                        $fieldName
+                    );
+                    $messageTitle = $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.required.title');
+                    $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, $messageTitle, FlashMessage::WARNING, true);
+                    $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
+                    $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
+                    $defaultFlashMessageQueue->enqueue($flashMessage);
+                    throw new SiteValidationErrorException(
+                        'Field ' . $fieldName . ' is set to required, but received empty.',
+                        1521726421
+                    );
+                }
+            }
+            if (!empty(array_diff($evalArray, $handledEvals))) {
+                throw new \RuntimeException('At least one not implemented \'eval\' in list ' . $fieldConfig['eval'], 1522491734);
+            }
+        }
+        if (isset($fieldConfig['range']['lower'])) {
+            $fieldValue = (int)$fieldValue < (int)$fieldConfig['range']['lower'] ? (int)$fieldConfig['range']['lower'] : (int)$fieldValue;
+        }
+        if (isset($fieldConfig['range']['upper'])) {
+            $fieldValue = (int)$fieldValue > (int)$fieldConfig['range']['upper'] ? (int)$fieldConfig['range']['upper'] : (int)$fieldValue;
+        }
+        return $fieldValue;
+    }
+
+    /**
+     * Last sanitation method after all data has been gathered. Check integrity
+     * of full record, manipulate if possible, or throw exception if unfixable broken.
+     *
+     * @param array $newSysSiteData Incoming data
+     * @return array Updated data if needed
+     * @throws \RuntimeException
+     */
+    protected function validateFullStructure(array $newSysSiteData): array
+    {
+        $languageService = $this->getLanguageService();
+        // Verify there are not two error handlers with the same error code
+        if (isset($newSysSiteData['site']['errorHandling']) && is_array($newSysSiteData['site']['errorHandling'])) {
+            $uniqueCriteria = [];
+            $validChildren = [];
+            foreach ($newSysSiteData['site']['errorHandling'] as $child) {
+                if (!isset($child['errorCode'])) {
+                    throw new \RuntimeException('No errorCode found', 1521788518);
+                }
+                if (!in_array((int)$child['errorCode'], $uniqueCriteria, true)) {
+                    $uniqueCriteria[] = (int)$child['errorCode'];
+                    $validChildren[] = $child;
+                } else {
+                    $message = sprintf(
+                        $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.duplicateErrorCode.message'),
+                        $child['errorCode']
+                    );
+                    $messageTitle = $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.duplicateErrorCode.title');
+                    $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, $messageTitle, FlashMessage::WARNING, true);
+                    $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
+                    $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
+                    $defaultFlashMessageQueue->enqueue($flashMessage);
+                }
+            }
+            $newSysSiteData['site']['errorHandling'] = $validChildren;
+        }
+
+        // Verify there is only one inline child per sys_language record configured.
+        if (!isset($newSysSiteData['site']['languages']) || !is_array($newSysSiteData['site']['languages']) || count($newSysSiteData['site']['languages']) < 1) {
+            throw new \RuntimeException(
+                'No default language definition found. The interface does not allow this. Aborting',
+                1521789306
+            );
+        }
+        $uniqueCriteria = [];
+        $validChildren = [];
+        foreach ($newSysSiteData['site']['languages'] as $child) {
+            if (!isset($child['languageId'])) {
+                throw new \RuntimeException('languageId not found', 1521789455);
+            }
+            if (!in_array((int)$child['languageId'], $uniqueCriteria, true)) {
+                $uniqueCriteria[] = (int)$child['languageId'];
+                $validChildren[] = $child;
+            } else {
+                $message = sprintf(
+                    $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.duplicateLanguageId.title'),
+                    $child['languageId']
+                );
+                $messageTitle = $languageService->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:validation.duplicateLanguageId.title');
+                $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, $message, $messageTitle, FlashMessage::WARNING, true);
+                $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
+                $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
+                $defaultFlashMessageQueue->enqueue($flashMessage);
+            }
+        }
+        $newSysSiteData['site']['languages'] = $validChildren;
+
+        return $newSysSiteData;
+    }
+
+    /**
+     * Delete an existing configuration
+     *
+     * @param ServerRequestInterface $request
+     * @return ResponseInterface
+     */
+    protected function deleteAction(ServerRequestInterface $request): ResponseInterface
+    {
+        $siteIdentifier = $request->getQueryParams()['site'] ?? '';
+        if (empty($siteIdentifier)) {
+            throw new \RuntimeException('Not site identifier given', 1521565182);
+        }
+        // Verify site does exist, method throws if not
+        GeneralUtility::makeInstance(SiteConfiguration::class, Environment::getConfigPath() . '/sites')->delete($siteIdentifier);
+        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
+        $overviewRoute = $uriBuilder->buildUriFromRoute('site_configuration', ['action' => 'overview']);
+        return new RedirectResponse($overviewRoute);
+    }
+
+    /**
+     * Sets up the Fluid View.
+     *
+     * @param string $templateName
+     */
+    protected function initializeView(string $templateName): void
+    {
+        $this->view = GeneralUtility::makeInstance(StandaloneView::class);
+        $this->view->setTemplate($templateName);
+        $this->view->setTemplateRootPaths(['EXT:backend/Resources/Private/Templates/SiteConfiguration']);
+        $this->view->setPartialRootPaths(['EXT:backend/Resources/Private/Partials']);
+        $this->view->setLayoutRootPaths(['EXT:backend/Resources/Private/Layouts']);
+    }
+
+    /**
+     * Create document header buttons of "edit" action
+     */
+    protected function configureEditViewDocHeader(): void
+    {
+        $iconFactory = $this->moduleTemplate->getIconFactory();
+        $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
+        $lang = $this->getLanguageService();
+        $closeButton = $buttonBar->makeLinkButton()
+            ->setHref('#')
+            ->setClasses('t3js-editform-close')
+            ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.closeDoc'))
+            ->setIcon($iconFactory->getIcon('actions-close', Icon::SIZE_SMALL));
+        $saveButton = $buttonBar->makeInputButton()
+            ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.saveDoc'))
+            ->setName('_savedok')
+            ->setValue('1')
+            ->setForm('siteConfigurationController')
+            ->setIcon($iconFactory->getIcon('actions-document-save', Icon::SIZE_SMALL));
+        $saveAndCloseButton = $buttonBar->makeInputButton()
+            ->setName('_saveandclosedok')
+            ->setClasses('t3js-editform-submitButton')
+            ->setValue('1')
+            ->setForm('siteConfigurationController')
+            ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.saveCloseDoc'))
+            ->setIcon($iconFactory->getIcon('actions-document-save-close', Icon::SIZE_SMALL));
+        $saveSplitButton = $buttonBar->makeSplitButton();
+        $saveSplitButton->addItem($saveButton, true);
+        $saveSplitButton->addItem($saveAndCloseButton);
+        $buttonBar->addButton($closeButton);
+        $buttonBar->addButton($saveSplitButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
+    }
+
+    /**
+     * Create document header buttons of "overview" action
+     */
+    protected function configureOverViewDocHeader(): void
+    {
+        $iconFactory = $this->moduleTemplate->getIconFactory();
+        $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
+        $reloadButton = $buttonBar->makeLinkButton()
+            ->setHref(GeneralUtility::getIndpEnv('REQUEST_URI'))
+            ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.reload'))
+            ->setIcon($iconFactory->getIcon('actions-refresh', Icon::SIZE_SMALL));
+        $buttonBar->addButton($reloadButton, ButtonBar::BUTTON_POSITION_RIGHT);
+        if ($this->getBackendUser()->mayMakeShortcut()) {
+            $getVars = ['id', 'route'];
+            $shortcutButton = $buttonBar->makeShortcutButton()
+                ->setModuleName('site_configuration')
+                ->setGetVariables($getVars);
+            $buttonBar->addButton($shortcutButton, ButtonBar::BUTTON_POSITION_RIGHT);
+        }
+    }
+
+    /**
+     * Returns a list of pages that have 'is_siteroot' set
+     *
+     * @return array
+     */
+    protected function getAllSitePages(): array
+    {
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
+        $statement = $queryBuilder
+            ->select('*')
+            ->from('pages')
+            ->where(
+                $queryBuilder->expr()->eq('sys_language_uid', 0),
+                $queryBuilder->expr()->orX(
+                    $queryBuilder->expr()->eq('pid', 0),
+                    $queryBuilder->expr()->eq('is_siteroot', 1)
+                )
+            )
+            ->orderBy('pid')
+            ->addOrderBy('sorting')
+            ->execute();
+
+        $pages = [];
+        while ($row = $statement->fetch()) {
+            $row['rootline'] = BackendUtility::BEgetRootLine((int)$row['uid']);
+            array_pop($row['rootline']);
+            $row['rootline'] = array_reverse($row['rootline']);
+            $i = 0;
+            foreach ($row['rootline'] as &$record) {
+                $record['margin'] = $i++ * 20;
+            }
+            $pages[(int)$row['uid']] = $row;
+        }
+        return $pages;
+    }
+
+    /**
+     * @return LanguageService
+     */
+    protected function getLanguageService(): LanguageService
+    {
+        return $GLOBALS['LANG'];
+    }
+
+    /**
+     * @return BackendUserAuthentication
+     */
+    protected function getBackendUser(): BackendUserAuthentication
+    {
+        return $GLOBALS['BE_USER'];
+    }
+}
diff --git a/typo3/sysext/backend/Classes/Controller/SiteInlineAjaxController.php b/typo3/sysext/backend/Classes/Controller/SiteInlineAjaxController.php
new file mode 100644
index 0000000000000000000000000000000000000000..d732aeeeed9577bc8e54199baf02cb7d25ed3226
--- /dev/null
+++ b/typo3/sysext/backend/Classes/Controller/SiteInlineAjaxController.php
@@ -0,0 +1,405 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Backend\Controller;
+
+/*
+ * 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!
+ */
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Backend\Configuration\SiteTcaConfiguration;
+use TYPO3\CMS\Backend\Form\FormDataCompiler;
+use TYPO3\CMS\Backend\Form\FormDataGroup\SiteConfigurationDataGroup;
+use TYPO3\CMS\Backend\Form\InlineStackProcessor;
+use TYPO3\CMS\Backend\Form\NodeFactory;
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
+use TYPO3\CMS\Core\Http\JsonResponse;
+use TYPO3\CMS\Core\Utility\ArrayUtility;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\MathUtility;
+
+/**
+ * Site configuration FormEngine controller class. Receives inline "edit" and "new"
+ * commands to expand / create site configuration inline records
+ */
+class SiteInlineAjaxController extends AbstractFormEngineAjaxController
+{
+    /**
+     * Default constructor
+     */
+    public function __construct()
+    {
+        // Bring site TCA into global scope.
+        // @todo: We might be able to get rid of that later
+        $GLOBALS['TCA'] = array_merge($GLOBALS['TCA'], GeneralUtility::makeInstance(SiteTcaConfiguration::class)->getTca());
+    }
+
+    /**
+     * Inline "create" new child of site configuration child records
+     *
+     * @param ServerRequestInterface $request
+     * @return ResponseInterface
+     * @throws \RuntimeException
+     */
+    public function newInlineChildAction(ServerRequestInterface $request): ResponseInterface
+    {
+        $ajaxArguments = $request->getParsedBody()['ajax'] ?? $request->getQueryParams()['ajax'];
+        $parentConfig = $this->extractSignedParentConfigFromRequest((string)$ajaxArguments['context']);
+        $domObjectId = $ajaxArguments[0];
+        $inlineFirstPid = $this->getInlineFirstPidFromDomObjectId($domObjectId);
+        $childChildUid = null;
+        if (isset($ajaxArguments[1]) && MathUtility::canBeInterpretedAsInteger($ajaxArguments[1])) {
+            $childChildUid = (int)$ajaxArguments[1];
+        }
+        // Parse the DOM identifier, add the levels to the structure stack
+        $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
+        $inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
+        $inlineStackProcessor->injectAjaxConfiguration($parentConfig);
+        $inlineTopMostParent = $inlineStackProcessor->getStructureLevel(0);
+        // Parent, this table embeds the child table
+        $parent = $inlineStackProcessor->getStructureLevel(-1);
+        // Child, a record from this table should be rendered
+        $child = $inlineStackProcessor->getUnstableStructure();
+        if (MathUtility::canBeInterpretedAsInteger($child['uid'])) {
+            // If uid comes in, it is the id of the record neighbor record "create after"
+            $childVanillaUid = -1 * abs((int)$child['uid']);
+        } else {
+            // Else inline first Pid is the storage pid of new inline records
+            $childVanillaUid = (int)$inlineFirstPid;
+        }
+        $childTableName = $parentConfig['foreign_table'];
+
+        $defaultDatabaseRow = [];
+        if ($childTableName === 'sys_site_language') {
+            // Feed new sys_site_language row with data from sys_language record if possible
+            if ($childChildUid > 0) {
+                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_language');
+                $queryBuilder->getRestrictions()->removeByType(HiddenRestriction::class);
+                $row = $queryBuilder->select('*')->from('sys_language')
+                    ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($childChildUid, \PDO::PARAM_INT)))
+                    ->execute()->fetch();
+                if (empty($row)) {
+                    throw new \RuntimeException('Referenced sys_language row not found', 1521783937);
+                }
+                if (!empty($row['language_isocode'])) {
+                    $defaultDatabaseRow['iso-639-1'] = $row['language_isocode'];
+                    $defaultDatabaseRow['base'] = '/' . $row['language_isocode'] . '/';
+                }
+                if (!empty($row['flag']) && $row['flag'] === 'multiple') {
+                    $defaultDatabaseRow['flag'] = 'global';
+                } elseif (!empty($row)) {
+                    $defaultDatabaseRow['flag'] = $row['flag'];
+                }
+                if (!empty($row['title'])) {
+                    $defaultDatabaseRow['title'] = $row['title'];
+                }
+            }
+        }
+
+        $formDataGroup = GeneralUtility::makeInstance(SiteConfigurationDataGroup::class);
+        $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
+        $formDataCompilerInput = [
+            'command' => 'new',
+            'tableName' => $childTableName,
+            'vanillaUid' => $childVanillaUid,
+            'databaseRow' => $defaultDatabaseRow,
+            'isInlineChild' => true,
+            'inlineStructure' => $inlineStackProcessor->getStructure(),
+            'inlineFirstPid' => $inlineFirstPid,
+            'inlineParentUid' => $parent['uid'],
+            'inlineParentTableName' => $parent['table'],
+            'inlineParentFieldName' => $parent['field'],
+            'inlineParentConfig' => $parentConfig,
+            'inlineTopMostParentUid' => $inlineTopMostParent['uid'],
+            'inlineTopMostParentTableName' => $inlineTopMostParent['table'],
+            'inlineTopMostParentFieldName' => $inlineTopMostParent['field'],
+        ];
+        if ($childChildUid) {
+            $formDataCompilerInput['inlineChildChildUid'] = $childChildUid;
+        }
+        $childData = $formDataCompiler->compile($formDataCompilerInput);
+
+        if ($parentConfig['foreign_selector'] && $parentConfig['appearance']['useCombination']) {
+            throw new \RuntimeException('useCombination not implemented in sites module', 1522493094);
+        }
+
+        $childData['inlineParentUid'] = (int)$parent['uid'];
+        $childData['renderType'] = 'inlineRecordContainer';
+        $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
+        $childResult = $nodeFactory->create($childData)->render();
+
+        $jsonArray = [
+            'data' => '',
+            'stylesheetFiles' => [],
+            'scriptCall' => [],
+        ];
+
+        // The HTML-object-id's prefix of the dynamically created record
+        $objectName = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
+        $objectPrefix = $objectName . '-' . $child['table'];
+        $objectId = $objectPrefix . '-' . $childData['databaseRow']['uid'];
+        $expandSingle = $parentConfig['appearance']['expandSingle'];
+        if (!$child['uid']) {
+            $jsonArray['scriptCall'][] = 'inline.domAddNewRecord(\'bottom\',' . GeneralUtility::quoteJSvalue($objectName . '_records') . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',json.data);';
+            $jsonArray['scriptCall'][] = 'inline.memorizeAddRecord(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . GeneralUtility::quoteJSvalue($childData['databaseRow']['uid']) . ',null,' . GeneralUtility::quoteJSvalue($childChildUid) . ');';
+        } else {
+            $jsonArray['scriptCall'][] = 'inline.domAddNewRecord(\'after\',' . GeneralUtility::quoteJSvalue($domObjectId . '_div') . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',json.data);';
+            $jsonArray['scriptCall'][] = 'inline.memorizeAddRecord(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . GeneralUtility::quoteJSvalue($childData['databaseRow']['uid']) . ',' . GeneralUtility::quoteJSvalue($child['uid']) . ',' . GeneralUtility::quoteJSvalue($childChildUid) . ');';
+        }
+        $jsonArray = $this->mergeChildResultIntoJsonResult($jsonArray, $childResult);
+        if ($parentConfig['appearance']['useSortable']) {
+            $inlineObjectName = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
+            $jsonArray['scriptCall'][] = 'inline.createDragAndDropSorting(' . GeneralUtility::quoteJSvalue($inlineObjectName . '_records') . ');';
+        }
+        if (!$parentConfig['appearance']['collapseAll'] && $expandSingle) {
+            $jsonArray['scriptCall'][] = 'inline.collapseAllRecords(' . GeneralUtility::quoteJSvalue($objectId) . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . GeneralUtility::quoteJSvalue($childData['databaseRow']['uid']) . ');';
+        }
+        // Fade out and fade in the new record in the browser view to catch the user's eye
+        $jsonArray['scriptCall'][] = 'inline.fadeOutFadeIn(' . GeneralUtility::quoteJSvalue($objectId . '_div') . ');';
+
+        return new JsonResponse($jsonArray);
+    }
+
+    /**
+     * Show the details of site configuration child records.
+     *
+     * @param ServerRequestInterface $request
+     * @return ResponseInterface
+     * @throws \RuntimeException
+     */
+    public function openInlineChildAction(ServerRequestInterface $request): ResponseInterface
+    {
+        $ajaxArguments = $request->getParsedBody()['ajax'] ?? $request->getQueryParams()['ajax'];
+
+        $domObjectId = $ajaxArguments[0];
+        $inlineFirstPid = $this->getInlineFirstPidFromDomObjectId($domObjectId);
+        $parentConfig = $this->extractSignedParentConfigFromRequest((string)$ajaxArguments['context']);
+
+        // Parse the DOM identifier, add the levels to the structure stack
+        $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
+        $inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
+        $inlineStackProcessor->injectAjaxConfiguration($parentConfig);
+
+        // Parent, this table embeds the child table
+        $parent = $inlineStackProcessor->getStructureLevel(-1);
+        $parentFieldName = $parent['field'];
+
+        // Set flag in config so that only the fields are rendered
+        // @todo: Solve differently / rename / whatever
+        $parentConfig['renderFieldsOnly'] = true;
+
+        $parentData = [
+            'processedTca' => [
+                'columns' => [
+                    $parentFieldName => [
+                        'config' => $parentConfig,
+                    ],
+                ],
+            ],
+            'tableName' => $parent['table'],
+            'inlineFirstPid' => $inlineFirstPid,
+            // Hand over given original return url to compile stack. Needed if inline children compile links to
+            // another view (eg. edit metadata in a nested inline situation like news with inline content element image),
+            // so the back link is still the link from the original request. See issue #82525. This is additionally
+            // given down in TcaInline data provider to compiled children data.
+            'returnUrl' => $parentConfig['originalReturnUrl'],
+        ];
+
+        // Child, a record from this table should be rendered
+        $child = $inlineStackProcessor->getUnstableStructure();
+
+        $childData = $this->compileChild($parentData, $parentFieldName, (int)$child['uid'], $inlineStackProcessor->getStructure());
+
+        $childData['inlineParentUid'] = (int)$parent['uid'];
+        $childData['renderType'] = 'inlineRecordContainer';
+        $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
+        $childResult = $nodeFactory->create($childData)->render();
+
+        $jsonArray = [
+            'data' => '',
+            'stylesheetFiles' => [],
+            'scriptCall' => [],
+        ];
+
+        // The HTML-object-id's prefix of the dynamically created record
+        $objectPrefix = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid) . '-' . $child['table'];
+        $objectId = $objectPrefix . '-' . (int)$child['uid'];
+        $expandSingle = $parentConfig['appearance']['expandSingle'];
+        $jsonArray['scriptCall'][] = 'inline.domAddRecordDetails(' . GeneralUtility::quoteJSvalue($domObjectId) . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',' . ($expandSingle ? '1' : '0') . ',json.data);';
+        if ($parentConfig['foreign_unique']) {
+            $jsonArray['scriptCall'][] = 'inline.removeUsed(' . GeneralUtility::quoteJSvalue($objectPrefix) . ',\'' . (int)$child['uid'] . '\');';
+        }
+        $jsonArray = $this->mergeChildResultIntoJsonResult($jsonArray, $childResult);
+        if ($parentConfig['appearance']['useSortable']) {
+            $inlineObjectName = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($inlineFirstPid);
+            $jsonArray['scriptCall'][] = 'inline.createDragAndDropSorting(' . GeneralUtility::quoteJSvalue($inlineObjectName . '_records') . ');';
+        }
+        if (!$parentConfig['appearance']['collapseAll'] && $expandSingle) {
+            $jsonArray['scriptCall'][] = 'inline.collapseAllRecords(' . GeneralUtility::quoteJSvalue($objectId) . ',' . GeneralUtility::quoteJSvalue($objectPrefix) . ',\'' . (int)$child['uid'] . '\');';
+        }
+
+        return new JsonResponse($jsonArray);
+    }
+
+    /**
+     * Compile a full child record
+     *
+     * @param array $parentData Result array of parent
+     * @param string $parentFieldName Name of parent field
+     * @param int $childUid Uid of child to compile
+     * @param array $inlineStructure Current inline structure
+     * @return array Full result array
+     * @throws \RuntimeException
+     *
+     * @todo: This clones methods compileChild from TcaInline Provider. Find a better abstraction
+     * @todo: to also encapsulate the more complex scenarios with combination child and friends.
+     */
+    protected function compileChild(array $parentData, string $parentFieldName, int $childUid, array $inlineStructure): array
+    {
+        $parentConfig = $parentData['processedTca']['columns'][$parentFieldName]['config'];
+
+        $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
+        $inlineStackProcessor->initializeByGivenStructure($inlineStructure);
+        $inlineTopMostParent = $inlineStackProcessor->getStructureLevel(0);
+
+        // @todo: do not use stack processor here ...
+        $child = $inlineStackProcessor->getUnstableStructure();
+        $childTableName = $child['table'];
+
+        $formDataGroup = GeneralUtility::makeInstance(SiteConfigurationDataGroup::class);
+        $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
+        $formDataCompilerInput = [
+            'command' => 'edit',
+            'tableName' => $childTableName,
+            'vanillaUid' => (int)$childUid,
+            'returnUrl' => $parentData['returnUrl'],
+            'isInlineChild' => true,
+            'inlineStructure' => $inlineStructure,
+            'inlineFirstPid' => $parentData['inlineFirstPid'],
+            'inlineParentConfig' => $parentConfig,
+            'isInlineAjaxOpeningContext' => true,
+
+            // values of the current parent element
+            // it is always a string either an id or new...
+            'inlineParentUid' => $parentData['databaseRow']['uid'],
+            'inlineParentTableName' => $parentData['tableName'],
+            'inlineParentFieldName' => $parentFieldName,
+
+            // values of the top most parent element set on first level and not overridden on following levels
+            'inlineTopMostParentUid' => $inlineTopMostParent['uid'],
+            'inlineTopMostParentTableName' => $inlineTopMostParent['table'],
+            'inlineTopMostParentFieldName' => $inlineTopMostParent['field'],
+        ];
+        if ($parentConfig['foreign_selector'] && $parentConfig['appearance']['useCombination']) {
+            throw new \RuntimeException('useCombination not implemented in sites module', 1522493095);
+        }
+        return $formDataCompiler->compile($formDataCompilerInput);
+    }
+
+    /**
+     * Merge stuff from child array into json array.
+     * This method is needed since ajax handling methods currently need to put scriptCalls before and after child code.
+     *
+     * @param array $jsonResult Given json result
+     * @param array $childResult Given child result
+     * @return array Merged json array
+     */
+    protected function mergeChildResultIntoJsonResult(array $jsonResult, array $childResult): array
+    {
+        $jsonResult['data'] .= $childResult['html'];
+        $jsonResult['stylesheetFiles'] = [];
+        foreach ($childResult['stylesheetFiles'] as $stylesheetFile) {
+            $jsonResult['stylesheetFiles'][] = $this->getRelativePathToStylesheetFile($stylesheetFile);
+        }
+        if (!empty($childResult['inlineData'])) {
+            $jsonResult['scriptCall'][] = 'inline.addToDataArray(' . json_encode($childResult['inlineData']) . ');';
+        }
+        if (!empty($childResult['additionalJavaScriptSubmit'])) {
+            $additionalJavaScriptSubmit = implode('', $childResult['additionalJavaScriptSubmit']);
+            $additionalJavaScriptSubmit = str_replace([CR, LF], '', $additionalJavaScriptSubmit);
+            $jsonResult['scriptCall'][] = 'TBE_EDITOR.addActionChecks("submit", "' . addslashes($additionalJavaScriptSubmit) . '");';
+        }
+        foreach ($childResult['additionalJavaScriptPost'] as $singleAdditionalJavaScriptPost) {
+            $jsonResult['scriptCall'][] = $singleAdditionalJavaScriptPost;
+        }
+        if (!empty($childResult['additionalInlineLanguageLabelFiles'])) {
+            $labels = [];
+            foreach ($childResult['additionalInlineLanguageLabelFiles'] as $additionalInlineLanguageLabelFile) {
+                ArrayUtility::mergeRecursiveWithOverrule(
+                    $labels,
+                    $this->getLabelsFromLocalizationFile($additionalInlineLanguageLabelFile)
+                );
+            }
+            $javaScriptCode = [];
+            $javaScriptCode[] = 'if (typeof TYPO3 === \'undefined\' || typeof TYPO3.lang === \'undefined\') {';
+            $javaScriptCode[] = '   TYPO3.lang = {}';
+            $javaScriptCode[] = '}';
+            $javaScriptCode[] = 'var additionalInlineLanguageLabels = ' . json_encode($labels) . ';';
+            $javaScriptCode[] = 'for (var attributeName in additionalInlineLanguageLabels) {';
+            $javaScriptCode[] = '   if (typeof TYPO3.lang[attributeName] === \'undefined\') {';
+            $javaScriptCode[] = '       TYPO3.lang[attributeName] = additionalInlineLanguageLabels[attributeName]';
+            $javaScriptCode[] = '   }';
+            $javaScriptCode[] = '}';
+
+            $jsonResult['scriptCall'][] = implode(LF, $javaScriptCode);
+        }
+        $requireJsModule = $this->createExecutableStringRepresentationOfRegisteredRequireJsModules($childResult);
+        $jsonResult['scriptCall'] = array_merge($requireJsModule, $jsonResult['scriptCall']);
+
+        return $jsonResult;
+    }
+
+    /**
+     * Inline ajax helper method.
+     *
+     * Validates the config that is transferred over the wire to provide the
+     * correct TCA config for the parent table
+     *
+     * @param string $contextString
+     * @throws \RuntimeException
+     * @return array
+     */
+    protected function extractSignedParentConfigFromRequest(string $contextString): array
+    {
+        if ($contextString === '') {
+            throw new \RuntimeException('Empty context string given', 1522771624);
+        }
+        $context = json_decode($contextString, true);
+        if (empty($context['config'])) {
+            throw new \RuntimeException('Empty context config section given', 1522771632);
+        }
+        if (!hash_equals(GeneralUtility::hmac(json_encode($context['config']), 'InlineContext'), $context['hmac'])) {
+            throw new \RuntimeException('Hash does not validate', 1522771640);
+        }
+        return $context['config'];
+    }
+
+    /**
+     * Get inlineFirstPid from a given objectId string
+     *
+     * @param string $domObjectId The id attribute of an element
+     * @return int|null Pid or null
+     */
+    protected function getInlineFirstPidFromDomObjectId(string $domObjectId): ?int
+    {
+        // Substitute FlexForm addition and make parsing a bit easier
+        $domObjectId = str_replace('---', ':', $domObjectId);
+        // The starting pattern of an object identifier (e.g. "data-<firstPidValue>-<anything>)
+        $pattern = '/^data' . '-' . '(.+?)' . '-' . '(.+)$/';
+        if (preg_match($pattern, $domObjectId, $match)) {
+            return (int)$match[1];
+        }
+        return null;
+    }
+}
diff --git a/typo3/sysext/backend/Classes/Exception/SiteValidationErrorException.php b/typo3/sysext/backend/Classes/Exception/SiteValidationErrorException.php
new file mode 100644
index 0000000000000000000000000000000000000000..e7a84fa399f10c03a5b307cce0f4541db92a6d13
--- /dev/null
+++ b/typo3/sysext/backend/Classes/Exception/SiteValidationErrorException.php
@@ -0,0 +1,25 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Backend\Exception;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Backend\Exception;
+
+/**
+ * Exception thrown if site configuration for a page is not found
+ */
+class SiteValidationErrorException extends Exception
+{
+}
diff --git a/typo3/sysext/backend/Classes/Form/FieldInformation/SiteConfiguration.php b/typo3/sysext/backend/Classes/Form/FieldInformation/SiteConfiguration.php
new file mode 100644
index 0000000000000000000000000000000000000000..648f22ff1e7d3446a89713899319187f67e33c3d
--- /dev/null
+++ b/typo3/sysext/backend/Classes/Form/FieldInformation/SiteConfiguration.php
@@ -0,0 +1,51 @@
+<?php
+declare(strict_types=1);
+
+namespace TYPO3\CMS\Backend\Form\FieldInformation;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Backend\Form\AbstractNode;
+use TYPO3\CMS\Core\Localization\LanguageService;
+
+/**
+ * Provides field information texts for form engine fields concerning site configuration module
+ */
+class SiteConfiguration extends AbstractNode
+{
+    /**
+     * Handler for single nodes
+     *
+     * @return array As defined in initializeResultArray() of AbstractNode
+     */
+    public function render()
+    {
+        $resultArray = $this->initializeResultArray();
+        $fieldInformationText = $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/siteconfiguration_fieldinformation.xlf:' . $this->data['tableName'] . '.' . $this->data['fieldName']);
+        if ($fieldInformationText !== $this->data['fieldName']) {
+            $resultArray['html'] = $fieldInformationText;
+        }
+        return $resultArray;
+    }
+
+    /**
+     * Returns the LanguageService
+     *
+     * @return LanguageService
+     */
+    protected function getLanguageService()
+    {
+        return $GLOBALS['LANG'];
+    }
+}
diff --git a/typo3/sysext/backend/Classes/Form/FormDataGroup/SiteConfigurationDataGroup.php b/typo3/sysext/backend/Classes/Form/FormDataGroup/SiteConfigurationDataGroup.php
new file mode 100644
index 0000000000000000000000000000000000000000..f5b47e9b3cf7c19c79586aeabda1d242e7a97e1f
--- /dev/null
+++ b/typo3/sysext/backend/Classes/Form/FormDataGroup/SiteConfigurationDataGroup.php
@@ -0,0 +1,47 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Backend\Form\FormDataGroup;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Backend\Form\FormDataGroupInterface;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * A data provider group for site and language configuration.
+ *
+ * This data group is for data fetched from sites yml files,
+ * it is fed by "fake TCA" since there are no real db records.
+ *
+ * It's similar to "fullDatabaseRecord", with some unused TCA types
+ * kicked out and some own data providers for record data and inline handling.
+ */
+class SiteConfigurationDataGroup implements FormDataGroupInterface
+{
+    /**
+     * Compile form data
+     *
+     * @param array $result Initialized result array
+     * @return array Result filled with data
+     * @throws \UnexpectedValueException
+     */
+    public function compile(array $result)
+    {
+        $orderedProviderList = GeneralUtility::makeInstance(OrderedProviderList::class);
+        $orderedProviderList->setProviderList(
+            $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['formDataGroup']['siteConfiguration']
+        );
+        return $orderedProviderList->compile($result);
+    }
+}
diff --git a/typo3/sysext/backend/Classes/Form/FormDataProvider/SiteDatabaseEditRow.php b/typo3/sysext/backend/Classes/Form/FormDataProvider/SiteDatabaseEditRow.php
new file mode 100644
index 0000000000000000000000000000000000000000..b4f1a60558f2e2589409dcf14222904b991ca3bf
--- /dev/null
+++ b/typo3/sysext/backend/Classes/Form/FormDataProvider/SiteDatabaseEditRow.php
@@ -0,0 +1,72 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Backend\Form\FormDataProvider;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
+use TYPO3\CMS\Core\Site\SiteFinder;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Special data provider for the sites configuration module.
+ *
+ * Fetch "row" data from yml file and set as 'databaseRow'
+ */
+class SiteDatabaseEditRow implements FormDataProviderInterface
+{
+    /**
+     * First level of ['customData']['siteData'] to ['databaseRow']
+     *
+     * @param array $result
+     * @return array
+     * @throws \RuntimeException
+     */
+    public function addData(array $result): array
+    {
+        if ($result['command'] !== 'edit' || !empty($result['databaseRow'])) {
+            return $result;
+        }
+
+        $tableName = $result['tableName'];
+        $siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
+        if ($tableName === 'sys_site') {
+            $siteConfigurationForPageUid = (int)$result['vanillaUid'];
+            $rowData = $siteFinder->getSiteByRootPageId($siteConfigurationForPageUid)->getConfiguration();
+            $result['databaseRow']['uid'] = $rowData['rootPageId'];
+            $result['databaseRow']['identifier'] = $result['customData']['siteIdentifier'];
+        } elseif ($tableName === 'sys_site_errorhandling' || $tableName === 'sys_site_language') {
+            $siteConfigurationForPageUid = (int)($result['inlineTopMostParentUid'] ?? $result['inlineParentUid']);
+            $rowData = $siteFinder->getSiteByRootPageId($siteConfigurationForPageUid)->getConfiguration();
+            $parentFieldName = $result['inlineParentFieldName'];
+            if (!isset($rowData[$parentFieldName])) {
+                throw new \RuntimeException('Field "' . $parentFieldName . '" not found', 1520886092);
+            }
+            $rowData = $rowData[$parentFieldName][$result['vanillaUid']];
+            $result['databaseRow']['uid'] = $result['vanillaUid'];
+        } else {
+            throw new \RuntimeException('Other tables not implemented', 1520886234);
+        }
+
+        foreach ($rowData as $fieldName => $value) {
+            // Flat values only - databaseRow has no "tree"
+            if (!is_array($value)) {
+                $result['databaseRow'][$fieldName] = $value;
+            }
+        }
+        // All "records" are always on pid 0
+        $result['databaseRow']['pid'] = 0;
+        return $result;
+    }
+}
diff --git a/typo3/sysext/backend/Classes/Form/FormDataProvider/SiteTcaInline.php b/typo3/sysext/backend/Classes/Form/FormDataProvider/SiteTcaInline.php
new file mode 100644
index 0000000000000000000000000000000000000000..9b6e5400f2c04e9ba589b0d879732c9939e64481
--- /dev/null
+++ b/typo3/sysext/backend/Classes/Form/FormDataProvider/SiteTcaInline.php
@@ -0,0 +1,309 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Backend\Form\FormDataProvider;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Backend\Form\FormDataCompiler;
+use TYPO3\CMS\Backend\Form\FormDataGroup\OnTheFly;
+use TYPO3\CMS\Backend\Form\FormDataGroup\SiteConfigurationDataGroup;
+use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
+use TYPO3\CMS\Backend\Form\InlineStackProcessor;
+use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Site\SiteFinder;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Special data provider for the sites configuration module.
+ *
+ * Handle inline children of 'sys_site"
+ */
+class SiteTcaInline extends AbstractDatabaseRecordProvider implements FormDataProviderInterface
+{
+    /**
+     * Resolve inline fields
+     *
+     * @param array $result
+     * @return array
+     */
+    public function addData(array $result): array
+    {
+        $result = $this->addInlineFirstPid($result);
+        foreach ($result['processedTca']['columns'] as $fieldName => $fieldConfig) {
+            if (!$this->isInlineField($fieldConfig)) {
+                continue;
+            }
+            $childTableName = $fieldConfig['config']['foreign_table'];
+            if ($childTableName !== 'sys_site_errorhandling' && $childTableName !== 'sys_site_language') {
+                throw new \RuntimeException('Inline relation to other tables not implemented', 1522494737);
+            }
+            $result['processedTca']['columns'][$fieldName]['children'] = [];
+            $result = $this->resolveSiteRelatedChildren($result, $fieldName);
+            $result = $this->addForeignSelectorAndUniquePossibleRecords($result, $fieldName);
+        }
+
+        return $result;
+    }
+
+    /**
+     * Is column of type "inline"
+     *
+     * @param array $fieldConfig
+     * @return bool
+     */
+    protected function isInlineField(array $fieldConfig): bool
+    {
+        return !empty($fieldConfig['config']['type']) && $fieldConfig['config']['type'] === 'inline';
+    }
+
+    /**
+     * The "entry" pid for inline records. Nested inline records can potentially hang around on different
+     * pid's, but the entry pid is needed for AJAX calls, so that they would know where the action takes place on the page structure.
+     *
+     * @param array $result Incoming result
+     * @return array Modified result
+     * @todo: Find out when and if this is different from 'effectivePid'
+     */
+    protected function addInlineFirstPid(array $result): array
+    {
+        if ($result['inlineFirstPid'] === null) {
+            $table = $result['tableName'];
+            $row = $result['databaseRow'];
+            // If the parent is a page, use the uid(!) of the (new?) page as pid for the child records:
+            if ($table === 'pages') {
+                $liveVersionId = BackendUtility::getLiveVersionIdOfRecord('pages', $row['uid']);
+                $pid = $liveVersionId === null ? $row['uid'] : $liveVersionId;
+            } elseif ($row['pid'] < 0) {
+                $prevRec = BackendUtility::getRecord($table, abs($row['pid']));
+                $pid = $prevRec['pid'];
+            } else {
+                $pid = $row['pid'];
+            }
+            $pageRecord = BackendUtility::getRecord('pages', $pid);
+            if ((int)$pageRecord[$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField']] > 0) {
+                $pid = (int)$pageRecord[$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField']];
+            }
+            $result['inlineFirstPid'] = (int)$pid;
+        }
+        return $result;
+    }
+
+    /**
+     * Substitute the value in databaseRow of this inline field with an array
+     * that contains the databaseRows of currently connected records and some meta information.
+     *
+     * @param array $result Result array
+     * @param string $fieldName Current handle field name
+     * @return array Modified item array
+     */
+    protected function resolveSiteRelatedChildren(array $result, string $fieldName): array
+    {
+        $connectedUids = [];
+        if ($result['command'] === 'edit') {
+            $siteConfigurationForPageUid = (int)$result['databaseRow']['rootPageId'][0];
+            $siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
+            $site = $siteFinder->getSiteByRootPageId($siteConfigurationForPageUid);
+            $siteConfiguration = $site ? $site->getConfiguration() : [];
+            if (is_array($siteConfiguration[$fieldName])) {
+                $connectedUids = array_keys($siteConfiguration[$fieldName]);
+            }
+        }
+
+        // If we are dealing with sys_site_language, we *always* force a relation to sys_language "0"
+        $foreignTable = $result['processedTca']['columns'][$fieldName]['config']['foreign_table'];
+        if ($foreignTable === 'sys_site_language' && $result['command'] === 'new') {
+            // If new, just add a new default child
+            $child = $this->compileDefaultSysSiteLanguageChild($result, $fieldName);
+            $connectedUids[] = $child['databaseRow']['uid'];
+            $result['processedTca']['columns'][$fieldName]['children'][] = $child;
+        }
+
+        $result['databaseRow'][$fieldName] = implode(',', $connectedUids);
+        if ($result['inlineCompileExistingChildren']) {
+            foreach ($connectedUids as $uid) {
+                if (strpos((string)$uid, 'NEW') !== 0) {
+                    $compiledChild = $this->compileChild($result, $fieldName, $uid);
+                    $result['processedTca']['columns'][$fieldName]['children'][] = $compiledChild;
+                }
+            }
+        }
+
+        // If we are dealing with sys_site_language, we *always* force a relation to sys_language "0"
+        if ($foreignTable === 'sys_site_language' && $result['command'] === 'edit') {
+            // If edit, find out if a child using sys_language "0" exists, else add it on top
+            $defaultSysSiteLanguageChildFound = false;
+            foreach ($result['processedTca']['columns'][$fieldName]['children'] as $child) {
+                if (isset($child['databaseRow']['languageId']) && (int)$child['databaseRow']['languageId'][0] == 0) {
+                    $defaultSysSiteLanguageChildFound = true;
+                }
+            }
+            if (!$defaultSysSiteLanguageChildFound) {
+                // Compile and add child as first child
+                $child = $this->compileDefaultSysSiteLanguageChild($result, $fieldName);
+                $result['databaseRow'][$fieldName] = $child['databaseRow']['uid'] . ',' . $result['databaseRow'][$fieldName];
+                array_unshift($result['processedTca']['columns'][$fieldName]['children'], $child);
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * If there is a foreign_selector or foreign_unique configuration, fetch
+     * the list of possible records that can be connected and attach them to the
+     * inline configuration.
+     *
+     * @param array $result Result array
+     * @param string $fieldName Current handle field name
+     * @return array Modified item array
+     */
+    protected function addForeignSelectorAndUniquePossibleRecords(array $result, string $fieldName): array
+    {
+        if (!is_array($result['processedTca']['columns'][$fieldName]['config']['selectorOrUniqueConfiguration'])) {
+            return $result;
+        }
+
+        $selectorOrUniqueConfiguration = $result['processedTca']['columns'][$fieldName]['config']['selectorOrUniqueConfiguration'];
+        $foreignFieldName = $selectorOrUniqueConfiguration['fieldName'];
+        $selectorOrUniquePossibleRecords = [];
+
+        if ($selectorOrUniqueConfiguration['config']['type'] === 'select') {
+            // Compile child table data for this field only
+            $selectDataInput = [
+                'tableName' => $result['processedTca']['columns'][$fieldName]['config']['foreign_table'],
+                'command' => 'new',
+                // Since there is no existing record that may have a type, it does not make sense to
+                // do extra handling of pageTsConfig merged here. Just provide "parent" pageTS as is
+                'pageTsConfig' => $result['pageTsConfig'],
+                'userTsConfig' => $result['userTsConfig'],
+                'databaseRow' => $result['databaseRow'],
+                'processedTca' => [
+                    'ctrl' => [],
+                    'columns' => [
+                        $foreignFieldName => [
+                            'config' => $selectorOrUniqueConfiguration['config'],
+                        ],
+                    ],
+                ],
+                'inlineExpandCollapseStateArray' => $result['inlineExpandCollapseStateArray'],
+            ];
+            $formDataGroup = GeneralUtility::makeInstance(OnTheFly::class);
+            $formDataGroup->setProviderList([TcaSelectItems::class]);
+            $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
+            $compilerResult = $formDataCompiler->compile($selectDataInput);
+            $selectorOrUniquePossibleRecords = $compilerResult['processedTca']['columns'][$foreignFieldName]['config']['items'];
+        }
+
+        $result['processedTca']['columns'][$fieldName]['config']['selectorOrUniquePossibleRecords'] = $selectorOrUniquePossibleRecords;
+
+        return $result;
+    }
+
+    /**
+     * Compile a full child record
+     *
+     * @param array $result Result array of parent
+     * @param string $parentFieldName Name of parent field
+     * @param int $childUid Uid of child to compile
+     * @return array Full result array
+     */
+    protected function compileChild(array $result, string $parentFieldName, int $childUid): array
+    {
+        $parentConfig = $result['processedTca']['columns'][$parentFieldName]['config'];
+        $childTableName = $parentConfig['foreign_table'];
+
+        $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
+        $inlineStackProcessor->initializeByGivenStructure($result['inlineStructure']);
+        $inlineTopMostParent = $inlineStackProcessor->getStructureLevel(0);
+
+        $formDataGroup = GeneralUtility::makeInstance(SiteConfigurationDataGroup::class);
+        $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
+        $formDataCompilerInput = [
+            'command' => 'edit',
+            'tableName' => $childTableName,
+            'vanillaUid' => $childUid,
+            // Give incoming returnUrl down to children so they generate a returnUrl back to
+            // the originally opening record, also see "originalReturnUrl" in inline container
+            // and FormInlineAjaxController
+            'returnUrl' => $result['returnUrl'],
+            'isInlineChild' => true,
+            'inlineStructure' => $result['inlineStructure'],
+            'inlineExpandCollapseStateArray' => $result['inlineExpandCollapseStateArray'],
+            'inlineFirstPid' => $result['inlineFirstPid'],
+            'inlineParentConfig' => $parentConfig,
+
+            // values of the current parent element
+            // it is always a string either an id or new...
+            'inlineParentUid' => $result['databaseRow']['uid'],
+            'inlineParentTableName' => $result['tableName'],
+            'inlineParentFieldName' => $parentFieldName,
+
+            // values of the top most parent element set on first level and not overridden on following levels
+            'inlineTopMostParentUid' => $result['inlineTopMostParentUid'] ?: $inlineTopMostParent['uid'],
+            'inlineTopMostParentTableName' => $result['inlineTopMostParentTableName'] ?: $inlineTopMostParent['table'],
+            'inlineTopMostParentFieldName' => $result['inlineTopMostParentFieldName'] ?: $inlineTopMostParent['field'],
+        ];
+
+        if ($parentConfig['foreign_selector'] && $parentConfig['appearance']['useCombination']) {
+            throw new \RuntimeException('useCombination not implemented in sites module', 1522493097);
+        }
+        return $formDataCompiler->compile($formDataCompilerInput);
+    }
+
+    /**
+     * Compile default sys_site_language child using sys_language uid "0"
+     *
+     * @param array $result
+     * @param string $parentFieldName
+     * @return array
+     */
+    protected function compileDefaultSysSiteLanguageChild(array $result, string $parentFieldName): array
+    {
+        $parentConfig = $result['processedTca']['columns'][$parentFieldName]['config'];
+        $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
+        $inlineStackProcessor->initializeByGivenStructure($result['inlineStructure']);
+        $inlineTopMostParent = $inlineStackProcessor->getStructureLevel(0);
+        $formDataGroup = GeneralUtility::makeInstance(SiteConfigurationDataGroup::class);
+        $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
+        $formDataCompilerInput = [
+            'command' => 'new',
+            'tableName' => 'sys_site_language',
+            'vanillaUid' => $result['inlineFirstPid'],
+            'returnUrl' => $result['returnUrl'],
+            'isInlineChild' => true,
+            'inlineStructure' => [],
+            'inlineExpandCollapseStateArray' => $result['inlineExpandCollapseStateArray'],
+            'inlineFirstPid' => $result['inlineFirstPid'],
+            'inlineParentConfig' => $parentConfig,
+            'inlineParentUid' => $result['databaseRow']['uid'],
+            'inlineParentTableName' => $result['tableName'],
+            'inlineParentFieldName' => $parentFieldName,
+            'inlineTopMostParentUid' => $result['inlineTopMostParentUid'] ?: $inlineTopMostParent['uid'],
+            'inlineTopMostParentTableName' => $result['inlineTopMostParentTableName'] ?: $inlineTopMostParent['table'],
+            'inlineTopMostParentFieldName' => $result['inlineTopMostParentFieldName'] ?: $inlineTopMostParent['field'],
+            // The sys_language uid 0
+            'inlineChildChildUid' => 0,
+        ];
+        return $formDataCompiler->compile($formDataCompilerInput);
+    }
+
+    /**
+     * @return BackendUserAuthentication
+     */
+    protected function getBackendUser(): BackendUserAuthentication
+    {
+        return $GLOBALS['BE_USER'];
+    }
+}
diff --git a/typo3/sysext/backend/Classes/Form/FormDataProvider/SiteTcaSelectItems.php b/typo3/sysext/backend/Classes/Form/FormDataProvider/SiteTcaSelectItems.php
new file mode 100644
index 0000000000000000000000000000000000000000..958c92953776e6e11e4f73fead28fdec2f7c75aa
--- /dev/null
+++ b/typo3/sysext/backend/Classes/Form/FormDataProvider/SiteTcaSelectItems.php
@@ -0,0 +1,63 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Backend\Form\FormDataProvider;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
+use TYPO3\CMS\Core\Localization\Locales;
+use TYPO3\CMS\Core\Service\IsoCodeService;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Special data provider for the sites configuration module.
+ *
+ * Resolve some specialities of the "site configuration"
+ */
+class SiteTcaSelectItems implements FormDataProviderInterface
+{
+    /**
+     * Resolve select items for
+     * * 'sys_site_language' -> 'typo3language'
+     *
+     * @param array $result
+     * @return array
+     * @throws \UnexpectedValueException
+     */
+    public function addData(array $result): array
+    {
+        $table = $result['tableName'];
+        if ($table !== 'sys_site_language') {
+            return $result;
+        }
+
+        // Available languages from Locales class put as "typo3Language" items
+        $locales = GeneralUtility::makeInstance(Locales::class);
+        $languages = $locales->getLanguages();
+        $items = [];
+        foreach ($languages as $key => $label) {
+            $items[] = [
+                0 => $label,
+                1 => $key,
+            ];
+        }
+        $result['processedTca']['columns']['typo3Language']['config']['items'] = $items;
+
+        // Available ISO-639-1 codes fetch from service class and put as "iso-639-1" items
+        $isoItems = GeneralUtility::makeInstance(IsoCodeService::class)->renderIsoCodeSelectDropdown(['items' => []]);
+        $result['processedTca']['columns']['iso-639-1']['config']['items'] = $isoItems['items'];
+
+        return $result;
+    }
+}
diff --git a/typo3/sysext/backend/Classes/Middleware/SiteResolver.php b/typo3/sysext/backend/Classes/Middleware/SiteResolver.php
new file mode 100644
index 0000000000000000000000000000000000000000..f28021cb48c57ddaf43c9730c71f016162cac84a
--- /dev/null
+++ b/typo3/sysext/backend/Classes/Middleware/SiteResolver.php
@@ -0,0 +1,61 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Backend\Middleware;
+
+/*
+ * 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!
+ */
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\MiddlewareInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use TYPO3\CMS\Core\Exception\SiteNotFoundException;
+use TYPO3\CMS\Core\Site\SiteFinder;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Usually called after the route object is resolved, however, this is not possible yet as this happens
+ * within the RequestHandler/RouteDispatcher right now and should go away.
+ *
+ * This middleware checks for a "id" parameter. If present, it adds a site information to this page ID.
+ *
+ * Very useful for all "Web" related modules to resolve all available languages for a site.
+ */
+class SiteResolver implements MiddlewareInterface
+{
+    /**
+     * Resolve the site information by checking the page ID ("id" parameter) which is typically used in BE modules
+     * of type "web".
+     *
+     * @param ServerRequestInterface $request
+     * @param RequestHandlerInterface $handler
+     * @return ResponseInterface
+     */
+    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+    {
+        $finder = GeneralUtility::makeInstance(SiteFinder::class);
+        $site = null;
+        $pageId = (int)($request->getQueryParams()['id'] ?? $request->getParsedBody()['id'] ?? 0);
+
+        // Check if we have a _GET/_POST parameter for "id", then a site information can be resolved based.
+        if ($pageId > 0) {
+            try {
+                $site = $finder->getSiteByPageId($pageId);
+                $request = $request->withAttribute('site', $site);
+                $GLOBALS['TYPO3_REQUEST'] = $request;
+            } catch (SiteNotFoundException $e) {
+            }
+        }
+        return $handler->handle($request);
+    }
+}
diff --git a/typo3/sysext/backend/Classes/Routing/PageUriBuilder.php b/typo3/sysext/backend/Classes/Routing/PageUriBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..13e5cca310bb8205c9b44563d2dffea8e4b9e0fa
--- /dev/null
+++ b/typo3/sysext/backend/Classes/Routing/PageUriBuilder.php
@@ -0,0 +1,118 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Backend\Routing;
+
+/*
+ * 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!
+ */
+
+use Psr\Http\Message\UriInterface;
+use TYPO3\CMS\Core\Exception\SiteNotFoundException;
+use TYPO3\CMS\Core\Http\Uri;
+use TYPO3\CMS\Core\SingletonInterface;
+use TYPO3\CMS\Core\Site\SiteFinder;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Responsible for generates URLs to pages which are NOT bound to any permissions or frontend restrictions.
+ *
+ * If a page is built with a site in the root line, the base of the site (+ language) is used
+ * and the &L parameter is then dropped explicitly.
+ *
+ * @internal as this might change until TYPO3 v9 LTS
+ * @todo: check handling of MP parameter.
+ */
+class PageUriBuilder implements SingletonInterface
+{
+    /**
+     * Generates an absolute URL
+     */
+    const ABSOLUTE_URL = 'url';
+
+    /**
+     * Generates an absolute path
+     */
+    const ABSOLUTE_PATH = 'path';
+
+    /**
+     * @var SiteFinder
+     */
+    protected $siteFinder;
+
+    /**
+     * PageUriBuilder constructor.
+     */
+    public function __construct()
+    {
+        $this->siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
+    }
+
+    /**
+     * Main entrypoint for generating an Uri for a page.
+     *
+     * @param int $pageId
+     * @param array $queryParameters
+     * @param string $fragment
+     * @param array $options ['language' => 123, 'rootLine' => etc.]
+     * @param string $referenceType
+     * @return UriInterface
+     */
+    public function buildUri(int $pageId, array $queryParameters = [], string $fragment = null, array $options = [], string $referenceType = self::ABSOLUTE_PATH): UriInterface
+    {
+        // Resolve site
+        $languageOption = isset($options['language']) ? (int)$options['language'] : null;
+        $languageQueryParameter = isset($queryParameters['L']) ? (int)$queryParameters['L'] : null;
+        $languageId = $languageOption ?? $languageQueryParameter ?? null;
+
+        // alternative page ID - Used to set as alias as well
+        $alternativePageId = $options['alternativePageId'] ?? $pageId;
+        $siteLanguage = null;
+        try {
+            $site = $this->siteFinder->getSiteByPageId($pageId, $options['rootLine'] ?? null);
+            if ($site) {
+                // Resolve language (based on the options / query parameters, and remove it from GET variables,
+                // as the language is determined by the language path
+                unset($queryParameters['L']);
+                $siteLanguage = $site->getLanguageById($languageId ?? 0);
+            }
+        } catch (SiteNotFoundException | \InvalidArgumentException $e) {
+        }
+
+        // If something is found, use /en/?id=123&additionalParams
+        // Only if a language is configured for the site, build a URL with a site prefix / base
+        if ($siteLanguage) {
+            unset($options['legacyUrlPrefix']);
+            $prefix = $siteLanguage->getBase() . '?id=' . $alternativePageId;
+        } else {
+            // If nothing is found, use index.php?id=123&additionalParams
+            $prefix = $options['legacyUrlPrefix'] ?? null;
+            if ($prefix === null) {
+                $prefix = $referenceType === self::ABSOLUTE_URL ? GeneralUtility::getIndpEnv('TYPO3_SITE_URL') : '';
+            }
+            $prefix .= 'index.php?id=' . $alternativePageId;
+            if ($languageId !== null) {
+                $queryParameters['L'] = $languageId;
+            }
+        }
+
+        // Add the query parameters as string
+        $queryString = http_build_query($queryParameters, '', '&', PHP_QUERY_RFC3986);
+        $uri = new Uri($prefix . ($queryString ? '&' . $queryString : ''));
+        if ($fragment) {
+            $uri = $uri->withFragment($fragment);
+        }
+        if ($referenceType === self::ABSOLUTE_PATH && !isset($options['legacyUrlPrefix'])) {
+            $uri = $uri->withScheme('')->withHost('')->withPort(null);
+        }
+        return $uri;
+    }
+}
diff --git a/typo3/sysext/backend/Classes/Utility/BackendUtility.php b/typo3/sysext/backend/Classes/Utility/BackendUtility.php
index f22e7e6a2f19a0f1d1fe0f6589cd5da0b048dc86..076cdd3bce014b0a7e80ace3f7a282a91c37b749 100644
--- a/typo3/sysext/backend/Classes/Utility/BackendUtility.php
+++ b/typo3/sysext/backend/Classes/Utility/BackendUtility.php
@@ -15,6 +15,7 @@ namespace TYPO3\CMS\Backend\Utility;
  */
 
 use Psr\Log\LoggerInterface;
+use TYPO3\CMS\Backend\Routing\PageUriBuilder;
 use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Cache\CacheManager;
@@ -27,6 +28,7 @@ use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
 use TYPO3\CMS\Core\Database\RelationHandler;
+use TYPO3\CMS\Core\Exception\SiteNotFoundException;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
@@ -37,6 +39,7 @@ use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
 use TYPO3\CMS\Core\Resource\File;
 use TYPO3\CMS\Core\Resource\ProcessedFile;
 use TYPO3\CMS\Core\Resource\ResourceFactory;
+use TYPO3\CMS\Core\Site\SiteFinder;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
@@ -2569,7 +2572,20 @@ class BackendUtility
             $permissionClause = $GLOBALS['BE_USER']->getPagePermsClause(Permission::PAGE_SHOW);
             $pageInfo = self::readPageAccess($pageUid, $permissionClause);
             $additionalGetVars .= self::ADMCMD_previewCmds($pageInfo);
-            $previewUrl = self::createPreviewUrl($pageUid, $rootLine, $anchorSection, $additionalGetVars, $viewScript);
+
+            // Build the URL with a site as prefix, if configured
+            $siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
+            // Check if the page (= its rootline) has a site attached, otherwise just keep the URL as is
+            $rootLine = $rootLine ?? BackendUtility::BEgetRootLine($pageUid);
+            try {
+                $site = $siteFinder->getSiteByPageId((int)$pageUid, $rootLine);
+                // Create a multi-dimensional array out of the additional get vars
+                $additionalGetVars = GeneralUtility::explodeUrl2Array($additionalGetVars, true);
+                $uriBuilder = GeneralUtility::makeInstance(PageUriBuilder::class);
+                $previewUrl = (string)$uriBuilder->buildUri($pageUid, $additionalGetVars, $anchorSection, ['rootLine' => $rootLine], $uriBuilder::ABSOLUTE_URL);
+            } catch (SiteNotFoundException $e) {
+                $previewUrl = self::createPreviewUrl($pageUid, $rootLine, $anchorSection, $additionalGetVars, $viewScript);
+            }
         }
 
         foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['viewOnClickClass'] ?? [] as $className) {
diff --git a/typo3/sysext/backend/Configuration/Backend/AjaxRoutes.php b/typo3/sysext/backend/Configuration/Backend/AjaxRoutes.php
index 5f1383e6dfdc840472936d2be7d81ea18d25af3d..002a36add17145449fa68666287d0212fad13db4 100644
--- a/typo3/sysext/backend/Configuration/Backend/AjaxRoutes.php
+++ b/typo3/sysext/backend/Configuration/Backend/AjaxRoutes.php
@@ -53,6 +53,18 @@ return [
         'target' => Controller\FormInlineAjaxController::class . '::expandOrCollapseAction'
     ],
 
+    // Site configuration inline create route
+    'site_configuration_inline_create' => [
+        'path' => '/siteconfiguration/inline/create',
+        'target' => Controller\SiteInlineAjaxController::class . '::newInlineChildAction'
+    ],
+
+    // Site configuration inline open existing "record" route
+    'site_configuration_inline_details' => [
+        'path' => '/siteconfiguration/inline/details',
+        'target' => Controller\SiteInlineAjaxController::class . '::openInlineChildAction'
+    ],
+
     // Add a flex form section container
     'record_flex_container_add' => [
         'path' => '/record/flex/containeradd',
diff --git a/typo3/sysext/backend/Configuration/RequestMiddlewares.php b/typo3/sysext/backend/Configuration/RequestMiddlewares.php
index d29b921f8b75b4a977e7ae989f23a561ee795719..bb8048e441a84f04957a18c142b9da794c4d9d7d 100644
--- a/typo3/sysext/backend/Configuration/RequestMiddlewares.php
+++ b/typo3/sysext/backend/Configuration/RequestMiddlewares.php
@@ -39,6 +39,12 @@ return [
                 'typo3/cms-backend/backend-routing'
             ]
         ],
+        'typo3/cms-backend/site-resolver' => [
+            'target' => \TYPO3\CMS\Backend\Middleware\SiteResolver::class,
+            'after' => [
+                'typo3/cms-backend/backend-routing'
+            ]
+        ],
         'typo3/cms-backend/legacy-document-template' => [
             'target' => \TYPO3\CMS\Backend\Middleware\LegacyBackendTemplateInitialization::class,
             'after' => [
diff --git a/typo3/sysext/backend/Configuration/SiteConfigurationTCA/sys_site.php b/typo3/sysext/backend/Configuration/SiteConfigurationTCA/sys_site.php
new file mode 100644
index 0000000000000000000000000000000000000000..834cfb3f6019697cb5ea992768883de6d4f827d7
--- /dev/null
+++ b/typo3/sysext/backend/Configuration/SiteConfigurationTCA/sys_site.php
@@ -0,0 +1,91 @@
+<?php
+
+return [
+    'ctrl' => [
+        'label' => 'identifier',
+        'title' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site.ctrl.title',
+        'typeicon_classes' => [
+            'default' => 'mimetypes-x-content-domain',
+        ],
+    ],
+    'columns' => [
+        'identifier' => [
+            'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site.identifier',
+            'config' => [
+                'type' => 'input',
+                'size' => 35,
+                'max' => 255,
+                // identifier is used as directory name - allow a-z,0-9,_,- as chars only.
+                // unique is additionally checked server side
+                'eval' => 'required,lower,alphanum_x',
+                'fieldInformation' => [
+                    'SiteConfigurationModuleFieldInformation' => [
+                        'renderType' => 'SiteConfigurationModuleFieldInformation',
+                    ],
+                ],
+            ],
+        ],
+        'rootPageId' => [
+            'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site.rootPageId',
+            'config' => [
+                'type' => 'select',
+                'readOnly' => true,
+                'renderType' => 'selectSingle',
+                'foreign_table' => 'pages',
+                'foreign_table_where' => ' AND (is_siteroot=1 OR (pid=0 AND doktype IN (1,6,7))) AND l10n_parent = 0 ORDER BY pid, sorting',
+                'fieldInformation' => [
+                    'SiteConfigurationModuleFieldInformation' => [
+                        'renderType' => 'SiteConfigurationModuleFieldInformation',
+                    ],
+                ],
+            ],
+        ],
+        'base' => [
+            'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site.base',
+            'config' => [
+                'type' => 'input',
+                'eval' => 'required',
+                'fieldInformation' => [
+                    'SiteConfigurationModuleFieldInformation' => [
+                        'renderType' => 'SiteConfigurationModuleFieldInformation',
+                    ],
+                ],
+            ],
+        ],
+        'languages' => [
+            'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site.languages',
+            'config' => [
+                'type' => 'inline',
+                'foreign_table' => 'sys_site_language',
+                'foreign_selector' => 'languageId',
+                'foreign_unique' => 'languageId',
+                'size' => 4,
+                'minitems' => 1,
+                'appearance' => [
+                    'enabledControls' => [
+                        'info' => false,
+                    ],
+                ],
+            ],
+        ],
+        'errorHandling' => [
+            'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site.errorHandling',
+            'config' => [
+                'type' => 'inline',
+                'foreign_table' => 'sys_site_errorhandling',
+                'appearance' => [
+                    'enabledControls' => [
+                        'info' => false,
+                    ],
+                ],
+            ],
+        ],
+    ],
+    'types' => [
+        '0' => [
+            'showitem' => 'identifier, rootPageId, base,
+                --div--;LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site.tab.languages, languages,
+                --div--;LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site.tab.errorHandling, errorHandling',
+        ],
+    ],
+];
diff --git a/typo3/sysext/backend/Configuration/SiteConfigurationTCA/sys_site_errorhandling.php b/typo3/sysext/backend/Configuration/SiteConfigurationTCA/sys_site_errorhandling.php
new file mode 100644
index 0000000000000000000000000000000000000000..c90677310a2e908093b9eb9a69f24d426669d7c0
--- /dev/null
+++ b/typo3/sysext/backend/Configuration/SiteConfigurationTCA/sys_site_errorhandling.php
@@ -0,0 +1,157 @@
+<?php
+
+return [
+    'ctrl' => [
+        'label' => 'errorCode',
+        'title' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_errorhandling.ctrl.title',
+        'type' => 'errorHandler',
+        'typeicon_column' => 'errorHandler',
+        'typeicon_classes' => [
+            'default' => 'default-not-found',
+            'Fluid' => 'mimetypes-text-html',
+            'ContentFromPid' => 'apps-pagetree-page-content-from-page',
+            'ClassDispatcher' => 'mimetypes-text-php',
+        ],
+    ],
+    'columns' => [
+        'errorCode' => [
+            'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_errorhandling.errorCode',
+            'config' => [
+                'type' => 'input',
+                'eval' => 'required, trim, int',
+                'range' => [
+                    'lower' => 0,
+                    'upper' => 599,
+                ],
+                'default' => 404,
+                'valuePicker' => [
+                    'mode' => '',
+                    'items' => [
+                        ['LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_errorhandling.errorCode.404', '404'],
+                        ['LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_errorhandling.errorCode.403', '403'],
+                        ['LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_errorhandling.errorCode.401', '401'],
+                        ['LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_errorhandling.errorCode.500', '500'],
+                        ['LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_errorhandling.errorCode.503', '503'],
+                        ['LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_errorhandling.errorCode.0', '0'],
+                    ],
+                ],
+                'fieldInformation' => [
+                    'SiteConfigurationModuleFieldInformation' => [
+                        'renderType' => 'SiteConfigurationModuleFieldInformation',
+                    ],
+                ],
+            ],
+        ],
+        'errorHandler' => [
+            'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_errorhandling.errorHandler',
+            'config' => [
+                'type' => 'select',
+                'renderType' => 'selectSingle',
+                'items' => [
+                    [' - select an handler type - ', ''],
+                    ['Fluid Template', 'Fluid'],
+                    ['Show Content from Page', 'Page'],
+                    ['PHP Class (must implement the PageErrorHandlerInterface)', 'PHP'],
+                ],
+                'fieldInformation' => [
+                    'SiteConfigurationModuleFieldInformation' => [
+                        'renderType' => 'SiteConfigurationModuleFieldInformation',
+                    ],
+                ],
+            ],
+        ],
+        'errorFluidTemplate' => [
+            'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_errorhandling.errorFluidTemplate',
+            'config' => [
+                'type' => 'input',
+                'eval' => 'required',
+                'fieldInformation' => [
+                    'SiteConfigurationModuleFieldInformation' => [
+                        'renderType' => 'SiteConfigurationModuleFieldInformation',
+                    ],
+                ],
+            ],
+        ],
+        'errorFluidTemplatesRootPath' => [
+            'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_errorhandling.errorFluidTemplatesRootPath',
+            'config' => [
+                'type' => 'input',
+                'fieldInformation' => [
+                    'SiteConfigurationModuleFieldInformation' => [
+                        'renderType' => 'SiteConfigurationModuleFieldInformation',
+                    ],
+                ],
+            ],
+        ],
+        'errorFluidLayoutsRootPath' => [
+            'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_errorhandling.errorFluidLayoutsRootPath',
+            'config' => [
+                'type' => 'input',
+                'fieldInformation' => [
+                    'SiteConfigurationModuleFieldInformation' => [
+                        'renderType' => 'SiteConfigurationModuleFieldInformation',
+                    ],
+                ],
+            ],
+        ],
+        'errorFluidPartialsRootPath' => [
+            'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_errorhandling.errorFluidPartialsRootPath',
+            'config' => [
+                'type' => 'input',
+                'fieldInformation' => [
+                    'SiteConfigurationModuleFieldInformation' => [
+                        'renderType' => 'SiteConfigurationModuleFieldInformation',
+                    ],
+                ],
+            ],
+        ],
+        'errorContentSource' => [
+            'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_errorhandling.errorContentSource',
+            'config' => [
+                'type' => 'input',
+                'renderType' => 'inputLink',
+                'eval' => 'required',
+                'fieldInformation' => [
+                    'SiteConfigurationModuleFieldInformation' => [
+                        'renderType' => 'SiteConfigurationModuleFieldInformation',
+                    ],
+                ],
+                'fieldControl' => [
+                    'linkPopup' => [
+                        'options' => [
+                            'blindLinkOptions' => 'file,mail,spec,folder',
+                        ]
+                    ]
+                ],
+            ],
+        ],
+        'errorPhpClassFQCN' => [
+            'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_errorhandling.errorPhpClassFQCN',
+            'config' => [
+                'type' => 'input',
+                'eval' => 'required',
+                'fieldInformation' => [
+                    'SiteConfigurationModuleFieldInformation' => [
+                        'renderType' => 'SiteConfigurationModuleFieldInformation',
+                    ],
+                ],
+            ],
+        ],
+    ],
+    'types' => [
+        '1' => [
+            'showitem' => 'errorCode, errorHandler',
+        ],
+        'Fluid' => [
+            'showitem' => 'errorCode, errorHandler, errorFluidTemplate,
+                           --div--;LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_errorhandling.tab.rootpaths,
+                           errorFluidTemplatesRootPath, errorFluidLayoutsRootPath, errorFluidPartialsRootPath',
+        ],
+        'Page' => [
+            'showitem' => 'errorCode, errorHandler, errorContentSource',
+        ],
+        'PHP' => [
+            'showitem' => 'errorCode, errorHandler, errorPhpClassFQCN',
+        ],
+    ],
+];
diff --git a/typo3/sysext/backend/Configuration/SiteConfigurationTCA/sys_site_language.php b/typo3/sysext/backend/Configuration/SiteConfigurationTCA/sys_site_language.php
new file mode 100644
index 0000000000000000000000000000000000000000..389ac30b3875ff3d1bf6d14ebc37b5c188cad5d9
--- /dev/null
+++ b/typo3/sysext/backend/Configuration/SiteConfigurationTCA/sys_site_language.php
@@ -0,0 +1,456 @@
+<?php
+
+return [
+    'ctrl' => [
+        'label' => 'languageId',
+        'title' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_language.ctrl.title',
+        'typeicon_classes' => [
+            'default' => 'mimetypes-x-content-domain',
+        ],
+    ],
+    'columns' => [
+        'languageId' => [
+            'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_language.language',
+            'config' => [
+                'type' => 'select',
+                'renderType' => 'selectSingle',
+                'items' => [
+                    ['Default Language', 0],
+                ],
+                'foreign_table' => 'sys_language',
+                'size' => 1,
+                'min' => 1,
+                'max' => 1,
+                'fieldInformation' => [
+                    'SiteConfigurationModuleFieldInformation' => [
+                        'renderType' => 'SiteConfigurationModuleFieldInformation',
+                    ],
+                ],
+            ],
+        ],
+        'title' => [
+            'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_language.title',
+            'config' => [
+                'type' => 'input',
+                'size' => 10,
+                'eval' => 'required',
+                'placeholder' => 'English',
+                'fieldInformation' => [
+                    'SiteConfigurationModuleFieldInformation' => [
+                        'renderType' => 'SiteConfigurationModuleFieldInformation',
+                    ],
+                ],
+            ],
+        ],
+        'navigationTitle' => [
+            'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_language.navigationTitle',
+            'config' => [
+                'type' => 'input',
+                'size' => 10,
+                'placeholder' => 'English',
+                'fieldInformation' => [
+                    'SiteConfigurationModuleFieldInformation' => [
+                        'renderType' => 'SiteConfigurationModuleFieldInformation',
+                    ],
+                ],
+            ],
+        ],
+        'base' => [
+            'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_language.base',
+            'config' => [
+                'type' => 'input',
+                'eval' => 'required',
+                'default' => '/',
+                'fieldInformation' => [
+                    'SiteConfigurationModuleFieldInformation' => [
+                        'renderType' => 'SiteConfigurationModuleFieldInformation',
+                    ],
+                ],
+            ],
+        ],
+        'locale' => [
+            'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_language.locale',
+            'config' => [
+                'type' => 'input',
+                'eval' => 'required',
+                'placeholder' => 'en_US.UTF-8',
+                'fieldInformation' => [
+                    'SiteConfigurationModuleFieldInformation' => [
+                        'renderType' => 'SiteConfigurationModuleFieldInformation',
+                    ],
+                ],
+            ],
+        ],
+        'iso-639-1' => [
+            'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_language.iso-639-1',
+            'config' => [
+                'type' => 'select',
+                'renderType' => 'selectSingle',
+                // Fed by data provider
+                'items' => [],
+                'fieldInformation' => [
+                    'SiteConfigurationModuleFieldInformation' => [
+                        'renderType' => 'SiteConfigurationModuleFieldInformation',
+                    ],
+                ],
+            ],
+        ],
+        'hreflang' => [
+            'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_language.hreflang',
+            'config' => [
+                'type' => 'input',
+                'placeholder' => 'en-US',
+                'fieldInformation' => [
+                    'SiteConfigurationModuleFieldInformation' => [
+                        'renderType' => 'SiteConfigurationModuleFieldInformation',
+                    ],
+                ],
+            ],
+        ],
+        'direction' => [
+            'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_language.direction',
+            'config' => [
+                'type' => 'select',
+                'renderType' => 'selectSingle',
+                'items' => [
+                    ['None', '', ''],
+                    ['Left to Right', 'ltr', ''],
+                    ['Right to Left', 'rtl', ''],
+                ],
+                'fieldInformation' => [
+                    'SiteConfigurationModuleFieldInformation' => [
+                        'renderType' => 'SiteConfigurationModuleFieldInformation',
+                    ],
+                ],
+            ],
+        ],
+        'typo3Language' => [
+            'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_language.typo3Language',
+            'config' => [
+                'type' => 'select',
+                'renderType' => 'selectSingle',
+                // Fed by data provider
+                'items' => [],
+                'fieldInformation' => [
+                    'SiteConfigurationModuleFieldInformation' => [
+                        'renderType' => 'SiteConfigurationModuleFieldInformation',
+                    ],
+                ],
+            ],
+        ],
+        'flag' => [
+            'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_language.flag',
+            'config' => [
+                'type' => 'select',
+                'renderType' => 'selectSingle',
+                'items' => [
+                    ['global', 'global', 'flags-multiple'],
+                    ['ad', 'ad', 'flags-ad'],
+                    ['ae', 'ae', 'flags-ae'],
+                    ['af', 'af', 'flags-af'],
+                    ['ag', 'ag', 'flags-ag'],
+                    ['ai', 'ai', 'flags-ai'],
+                    ['al', 'al', 'flags-al'],
+                    ['am', 'am', 'flags-am'],
+                    ['an', 'an', 'flags-an'],
+                    ['ao', 'ao', 'flags-ao'],
+                    ['ar', 'ar', 'flags-ar'],
+                    ['as', 'as', 'flags-as'],
+                    ['at', 'at', 'flags-at'],
+                    ['au', 'au', 'flags-au'],
+                    ['aw', 'aw', 'flags-aw'],
+                    ['ax', 'ax', 'flags-ax'],
+                    ['az', 'az', 'flags-az'],
+                    ['ba', 'ba', 'flags-ba'],
+                    ['bb', 'bb', 'flags-bb'],
+                    ['bd', 'bd', 'flags-bd'],
+                    ['be', 'be', 'flags-be'],
+                    ['bf', 'bf', 'flags-bf'],
+                    ['bg', 'bg', 'flags-bg'],
+                    ['bh', 'bh', 'flags-bh'],
+                    ['bi', 'bi', 'flags-bi'],
+                    ['bj', 'bj', 'flags-bj'],
+                    ['bm', 'bm', 'flags-bm'],
+                    ['bn', 'bn', 'flags-bn'],
+                    ['bo', 'bo', 'flags-bo'],
+                    ['br', 'br', 'flags-br'],
+                    ['bs', 'bs', 'flags-bs'],
+                    ['bt', 'bt', 'flags-bt'],
+                    ['bv', 'bv', 'flags-bv'],
+                    ['bw', 'bw', 'flags-bw'],
+                    ['by', 'by', 'flags-by'],
+                    ['bz', 'bz', 'flags-bz'],
+                    ['ca', 'ca', 'flags-ca'],
+                    ['catalonia', 'catalonia', 'flags-catalonia'],
+                    ['cc', 'cc', 'flags-cc'],
+                    ['cd', 'cd', 'flags-cd'],
+                    ['cf', 'cf', 'flags-cf'],
+                    ['cg', 'cg', 'flags-cg'],
+                    ['ch', 'ch', 'flags-ch'],
+                    ['ci', 'ci', 'flags-ci'],
+                    ['ck', 'ck', 'flags-ck'],
+                    ['cl', 'cl', 'flags-cl'],
+                    ['cm', 'cm', 'flags-cm'],
+                    ['cn', 'cn', 'flags-cn'],
+                    ['co', 'co', 'flags-co'],
+                    ['cr', 'cr', 'flags-cr'],
+                    ['cs', 'cs', 'flags-cs'],
+                    ['cu', 'cu', 'flags-cu'],
+                    ['cv', 'cv', 'flags-cv'],
+                    ['cx', 'cx', 'flags-cx'],
+                    ['cy', 'cy', 'flags-cy'],
+                    ['cz', 'cz', 'flags-cz'],
+                    ['de', 'de', 'flags-de'],
+                    ['dj', 'dj', 'flags-dj'],
+                    ['dk', 'dk', 'flags-dk'],
+                    ['dm', 'dm', 'flags-dm'],
+                    ['do', 'do', 'flags-do'],
+                    ['dz', 'dz', 'flags-dz'],
+                    ['ec', 'ec', 'flags-ec'],
+                    ['ee', 'ee', 'flags-ee'],
+                    ['eg', 'eg', 'flags-eg'],
+                    ['eh', 'eh', 'flags-eh'],
+                    ['en-us-gb', 'en-us-gb', 'flags-en-us-gb'],
+                    ['england', 'england', 'flags-gb-eng'],
+                    ['er', 'er', 'flags-er'],
+                    ['es', 'es', 'flags-es'],
+                    ['et', 'et', 'flags-et'],
+                    ['eu', 'eu', 'flags-eu'],
+                    ['fm', 'fm', 'flags-fm'],
+                    ['fi', 'fi', 'flags-fi'],
+                    ['fj', 'fj', 'flags-fj'],
+                    ['fk', 'fk', 'flags-fk'],
+                    ['fm', 'fm', 'flags-fm'],
+                    ['fo', 'fo', 'flags-fo'],
+                    ['fr', 'fr', 'flags-fr'],
+                    ['ga', 'ga', 'flags-ga'],
+                    ['gb', 'gb', 'flags-gb'],
+                    ['gd', 'gd', 'flags-gd'],
+                    ['ge', 'ge', 'flags-ge'],
+                    ['gf', 'gf', 'flags-gf'],
+                    ['gh', 'gh', 'flags-gh'],
+                    ['gi', 'gi', 'flags-gi'],
+                    ['gl', 'gl', 'flags-gl'],
+                    ['gm', 'gm', 'flags-gm'],
+                    ['gn', 'gn', 'flags-gn'],
+                    ['gp', 'gp', 'flags-gp'],
+                    ['gq', 'gq', 'flags-gq'],
+                    ['gr', 'gr', 'flags-gr'],
+                    ['gs', 'gs', 'flags-gs'],
+                    ['gt', 'gt', 'flags-gt'],
+                    ['gu', 'gu', 'flags-gu'],
+                    ['gw', 'gw', 'flags-gw'],
+                    ['gy', 'gy', 'flags-gy'],
+                    ['hk', 'hk', 'flags-hk'],
+                    ['hm', 'hm', 'flags-hm'],
+                    ['hn', 'hn', 'flags-hn'],
+                    ['hr', 'hr', 'flags-hr'],
+                    ['ht', 'ht', 'flags-ht'],
+                    ['hu', 'hu', 'flags-hu'],
+                    ['id', 'id', 'flags-id'],
+                    ['ie', 'ie', 'flags-ie'],
+                    ['il', 'il', 'flags-il'],
+                    ['in', 'in', 'flags-in'],
+                    ['io', 'io', 'flags-io'],
+                    ['iq', 'iq', 'flags-iq'],
+                    ['ir', 'ir', 'flags-ir'],
+                    ['is', 'is', 'flags-is'],
+                    ['it', 'it', 'flags-it'],
+                    ['jm', 'jm', 'flags-jm'],
+                    ['jo', 'jo', 'flags-jo'],
+                    ['jp', 'jp', 'flags-jp'],
+                    ['ke', 'ke', 'flags-ke'],
+                    ['kg', 'kg', 'flags-kg'],
+                    ['kh', 'kh', 'flags-kh'],
+                    ['ki', 'ki', 'flags-ki'],
+                    ['km', 'km', 'flags-km'],
+                    ['kn', 'kn', 'flags-kn'],
+                    ['kp', 'kp', 'flags-kp'],
+                    ['kr', 'kr', 'flags-kr'],
+                    ['kw', 'kw', 'flags-kw'],
+                    ['ky', 'ky', 'flags-ky'],
+                    ['kz', 'kz', 'flags-kz'],
+                    ['la', 'la', 'flags-la'],
+                    ['lb', 'lb', 'flags-lb'],
+                    ['lc', 'lc', 'flags-lc'],
+                    ['li', 'li', 'flags-li'],
+                    ['lk', 'lk', 'flags-lk'],
+                    ['lr', 'lr', 'flags-lr'],
+                    ['ls', 'ls', 'flags-ls'],
+                    ['lt', 'lt', 'flags-lt'],
+                    ['lu', 'lu', 'flags-lu'],
+                    ['lv', 'lv', 'flags-lv'],
+                    ['ly', 'ly', 'flags-ly'],
+                    ['ma', 'ma', 'flags-ma'],
+                    ['mc', 'mc', 'flags-mc'],
+                    ['md', 'md', 'flags-md'],
+                    ['me', 'me', 'flags-me'],
+                    ['mg', 'mg', 'flags-mg'],
+                    ['mh', 'mh', 'flags-mh'],
+                    ['mk', 'mk', 'flags-mk'],
+                    ['ml', 'ml', 'flags-ml'],
+                    ['mm', 'mm', 'flags-mm'],
+                    ['mn', 'mn', 'flags-mn'],
+                    ['mo', 'mo', 'flags-mo'],
+                    ['mp', 'mp', 'flags-mp'],
+                    ['mq', 'mq', 'flags-mq'],
+                    ['mr', 'mr', 'flags-mr'],
+                    ['ms', 'ms', 'flags-ms'],
+                    ['mt', 'mt', 'flags-mt'],
+                    ['mu', 'mu', 'flags-mu'],
+                    ['mv', 'mv', 'flags-mv'],
+                    ['mw', 'mw', 'flags-mw'],
+                    ['mx', 'mx', 'flags-mx'],
+                    ['my', 'my', 'flags-my'],
+                    ['mz', 'mz', 'flags-mz'],
+                    ['na', 'na', 'flags-na'],
+                    ['nc', 'nc', 'flags-nc'],
+                    ['ne', 'ne', 'flags-ne'],
+                    ['nf', 'nf', 'flags-nf'],
+                    ['ng', 'ng', 'flags-ng'],
+                    ['ni', 'ni', 'flags-ni'],
+                    ['nl', 'nl', 'flags-nl'],
+                    ['no', 'no', 'flags-no'],
+                    ['np', 'np', 'flags-np'],
+                    ['nr', 'nr', 'flags-nr'],
+                    ['nu', 'nu', 'flags-nu'],
+                    ['nz', 'nz', 'flags-nz'],
+                    ['om', 'om', 'flags-om'],
+                    ['pa', 'pa', 'flags-pa'],
+                    ['pe', 'pe', 'flags-pe'],
+                    ['pf', 'pf', 'flags-pf'],
+                    ['pg', 'pg', 'flags-pg'],
+                    ['ph', 'ph', 'flags-ph'],
+                    ['pk', 'pk', 'flags-pk'],
+                    ['pl', 'pl', 'flags-pl'],
+                    ['pm', 'pm', 'flags-pm'],
+                    ['pn', 'pn', 'flags-pn'],
+                    ['pr', 'pr', 'flags-pr'],
+                    ['ps', 'ps', 'flags-ps'],
+                    ['pt', 'pt', 'flags-pt'],
+                    ['pw', 'pw', 'flags-pw'],
+                    ['py', 'py', 'flags-py'],
+                    ['qa', 'qa', 'flags-qa'],
+                    ['qc', 'qc', 'flags-qc'],
+                    ['re', 're', 'flags-re'],
+                    ['ro', 'ro', 'flags-ro'],
+                    ['rs', 'rs', 'flags-rs'],
+                    ['ru', 'ru', 'flags-ru'],
+                    ['rw', 'rw', 'flags-rw'],
+                    ['sa', 'sa', 'flags-sa'],
+                    ['sb', 'sb', 'flags-sb'],
+                    ['sc', 'sc', 'flags-sc'],
+                    ['gb-sct', 'gb-sct', 'flags-gb-sct'],
+                    ['sd', 'sd', 'flags-sd'],
+                    ['se', 'se', 'flags-se'],
+                    ['sg', 'sg', 'flags-sg'],
+                    ['sh', 'sh', 'flags-sh'],
+                    ['si', 'si', 'flags-si'],
+                    ['sj', 'sj', 'flags-sj'],
+                    ['sk', 'sk', 'flags-sk'],
+                    ['sl', 'sl', 'flags-sl'],
+                    ['sm', 'sm', 'flags-sm'],
+                    ['sn', 'sn', 'flags-sn'],
+                    ['so', 'so', 'flags-so'],
+                    ['sr', 'sr', 'flags-sr'],
+                    ['st', 'st', 'flags-st'],
+                    ['sv', 'sv', 'flags-sv'],
+                    ['sy', 'sy', 'flags-sy'],
+                    ['sz', 'sz', 'flags-sz'],
+                    ['tc', 'tc', 'flags-tc'],
+                    ['td', 'td', 'flags-td'],
+                    ['tf', 'tf', 'flags-tf'],
+                    ['tg', 'tg', 'flags-tg'],
+                    ['th', 'th', 'flags-th'],
+                    ['tj', 'tj', 'flags-tj'],
+                    ['tk', 'tk', 'flags-tk'],
+                    ['tl', 'tl', 'flags-tl'],
+                    ['tm', 'tm', 'flags-tm'],
+                    ['tn', 'tn', 'flags-tn'],
+                    ['to', 'to', 'flags-to'],
+                    ['tr', 'tr', 'flags-tr'],
+                    ['tt', 'tt', 'flags-tt'],
+                    ['tv', 'tv', 'flags-tv'],
+                    ['tw', 'tw', 'flags-tw'],
+                    ['tz', 'tz', 'flags-tz'],
+                    ['ua', 'ua', 'flags-ua'],
+                    ['ug', 'ug', 'flags-ug'],
+                    ['um', 'um', 'flags-um'],
+                    ['us', 'us', 'flags-us'],
+                    ['uy', 'uy', 'flags-uy'],
+                    ['uz', 'uz', 'flags-uz'],
+                    ['va', 'va', 'flags-va'],
+                    ['vc', 'vc', 'flags-vc'],
+                    ['ve', 've', 'flags-ve'],
+                    ['vg', 'vg', 'flags-vg'],
+                    ['vi', 'vi', 'flags-vi'],
+                    ['vn', 'vn', 'flags-vn'],
+                    ['vu', 'vu', 'flags-vu'],
+                    ['gb-wls', 'gb-wls', 'flags-gb-wls'],
+                    ['wf', 'wf', 'flags-wf'],
+                    ['ws', 'ws', 'flags-ws'],
+                    ['ye', 'ye', 'flags-ye'],
+                    ['yt', 'yt', 'flags-yt'],
+                    ['za', 'za', 'flags-za'],
+                    ['zm', 'zm', 'flags-zm'],
+                    ['zw', 'zw', 'flags-zw'],
+                ],
+                'size' => 1,
+                'minitems' => 0,
+                'maxitems' => 1,
+                'fieldWizard' => [
+                    'selectIcons' => [
+                        'disabled' => false,
+                    ],
+                ],
+                'fieldInformation' => [
+                    'SiteConfigurationModuleFieldInformation' => [
+                        'renderType' => 'SiteConfigurationModuleFieldInformation',
+                    ],
+                ],
+            ],
+        ],
+        'fallbackType' => [
+            'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_language.fallbackType',
+            'displayCond' => 'FIELD:languageId:>:0',
+            'onChange' => 'reload',
+            'config' => [
+                'type' => 'select',
+                'renderType' => 'selectSingle',
+                'items' => [
+                    ['No fallback (strict)', 'strict'],
+                    ['Fallback to other language', 'fallback'],
+                ],
+                'fieldInformation' => [
+                    'SiteConfigurationModuleFieldInformation' => [
+                        'renderType' => 'SiteConfigurationModuleFieldInformation',
+                    ],
+                ],
+            ],
+        ],
+        'fallbacks' => [
+            'label' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf:sys_site_language.fallbacks',
+            'displayCond' => 'FIELD:fallbackType:=:fallback',
+            'config' => [
+                'type' => 'select',
+                'renderType' => 'selectMultipleSideBySide',
+                'items' => [
+                    ['Default Language', 0],
+                ],
+                'foreign_table' => 'sys_language',
+                'size' => 5,
+                'min' => 0,
+                'fieldInformation' => [
+                    'SiteConfigurationModuleFieldInformation' => [
+                        'renderType' => 'SiteConfigurationModuleFieldInformation',
+                    ],
+                ],
+            ],
+        ],
+    ],
+    'types' => [
+        '1' => [
+            'showitem' => 'languageId, title, navigationTitle, base, locale, iso-639-1, hreflang, direction, typo3Language, flag, fallbackType, fallbacks',
+        ],
+    ],
+];
diff --git a/typo3/sysext/backend/Resources/Private/Language/locallang_siteconfiguration.xlf b/typo3/sysext/backend/Resources/Private/Language/locallang_siteconfiguration.xlf
new file mode 100644
index 0000000000000000000000000000000000000000..cd16846f2189be9e58ed4b0491bcd2c4a8f22946
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Private/Language/locallang_siteconfiguration.xlf
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xliff version="1.0" xmlns:t3="http://typo3.org/schemas/xliff">
+	<file t3:id="1522780424" source-language="en" datatype="plaintext" original="messages" date="2018-02-27T22:22:32Z" product-name="backend">
+		<header/>
+		<body>
+			<trans-unit id="overview.title">
+				<source>Site Configuration</source>
+			</trans-unit>
+			<trans-unit id="overview.site">
+				<source>Site</source>
+			</trans-unit>
+			<trans-unit id="overview.configuration">
+				<source>Configuration Folder</source>
+			</trans-unit>
+			<trans-unit id="overview.noSiteConfiguration">
+				<source>This site does not have a configuration, yet.</source>
+			</trans-unit>
+			<trans-unit id="overview.addSiteConfiguration">
+				<source>Add new site configuration for this site</source>
+			</trans-unit>
+			<trans-unit id="overview.baseUrl">
+				<source>Base URLs</source>
+			</trans-unit>
+			<trans-unit id="validation.identifierRenamed.title">
+				<source>Renamed identifier</source>
+			</trans-unit>
+			<trans-unit id="validation.identifierRenamed.message">
+				<source>Given site identifier "%1s" already exists. It has been renamed to "%2s". Maybe you want to edit the site again and give it a better name.</source>
+			</trans-unit>
+			<trans-unit id="validation.identifierExists.title">
+				<source>Identifier not changed</source>
+			</trans-unit>
+			<trans-unit id="validation.identifierExists.message">
+				<source>Given site identifier "%1s" already exists and points to a different site. The existing identifier "%2s" for this site is kept.';</source>
+			</trans-unit>
+			<trans-unit id="validation.required.title">
+				<source>Site configuration not saved</source>
+			</trans-unit>
+			<trans-unit id="validation.required.message">
+				<source>Field "%1s" is a required field, but no value has been provided.</source>
+			</trans-unit>
+			<trans-unit id="validation.duplicateErrorCode.title">
+				<source>Duplicate error code removed</source>
+			</trans-unit>
+			<trans-unit id="validation.duplicateErrorCode.message">
+				<source>Two error handler configurations for error code "%1s" found. This would be an invalid configuration, the dangling one has been removed.</source>
+			</trans-unit>
+			<trans-unit id="validation.duplicateLanguageId.title">
+				<source>Duplicate language configuration removed</source>
+			</trans-unit>
+			<trans-unit id="validation.duplicateLanguageId.message">
+				<source>Two language configurations for language "%1s" found. This would be an invalid configuration, the dangling one has been removed.</source>
+			</trans-unit>
+			<trans-unit id="validation.duplicateLanguageId.message">
+				<source>Two language configurations for language "%1s" found. This would be an invalid configuration, the dangling one has been removed.</source>
+			</trans-unit>
+		</body>
+	</file>
+</xliff>
diff --git a/typo3/sysext/backend/Resources/Private/Language/locallang_siteconfiguration_module.xlf b/typo3/sysext/backend/Resources/Private/Language/locallang_siteconfiguration_module.xlf
new file mode 100644
index 0000000000000000000000000000000000000000..d8edf48ac3e7e46020a03178c032d66596f83d01
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Private/Language/locallang_siteconfiguration_module.xlf
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xliff version="1.0" xmlns:t3="http://typo3.org/schemas/xliff">
+	<file t3:id="1522771784" source-language="en" datatype="plaintext" original="messages" date="2018-02-27T22:22:32Z" product-name="backend">
+		<header/>
+		<body>
+			<trans-unit id="mlang_labels_tablabel">
+				<source>Site configuration</source>
+			</trans-unit>
+			<trans-unit id="mlang_labels_tabdescr">
+				<source>This module allows you to configure your entrypoints (called sites).</source>
+			</trans-unit>
+			<trans-unit id="mlang_tabs_tab">
+				<source>Configuration</source>
+			</trans-unit>
+		</body>
+	</file>
+</xliff>
diff --git a/typo3/sysext/backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf b/typo3/sysext/backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf
new file mode 100644
index 0000000000000000000000000000000000000000..f3a5fd2a068dc6524e183e2572eda76c0d439305
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Private/Language/locallang_siteconfiguration_tca.xlf
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xliff version="1.0" xmlns:t3="http://typo3.org/schemas/xliff">
+	<file t3:id="1522785604" source-language="en" datatype="plaintext" original="messages" date="2018-02-27T22:22:32Z" product-name="backend">
+		<header/>
+		<body>
+			<trans-unit id="sys_site.ctrl.title">
+				<source>Site Configuration</source>
+			</trans-unit>
+			<trans-unit id="sys_site.identifier">
+				<source>Site Identifier</source>
+			</trans-unit>
+			<trans-unit id="sys_site.rootPageId">
+				<source>Root Page ID (You must create a page with a site root flag)</source>
+			</trans-unit>
+			<trans-unit id="sys_site.base">
+				<source>Entry point (can be https://www.mydomain/ or just /, if it is just / you can not rely on TYPO3 creating full URLs)</source>
+			</trans-unit>
+			<trans-unit id="sys_site.languages">
+				<source>Available Languages for this site</source>
+			</trans-unit>
+			<trans-unit id="sys_site.errorHandling">
+				<source>Error Handling</source>
+			</trans-unit>
+			<trans-unit id="sys_site.tab.languages">
+				<source>Languages</source>
+			</trans-unit>
+			<trans-unit id="sys_site.tab.errorHandling">
+				<source>Error Handling</source>
+			</trans-unit>
+
+			<trans-unit id="sys_site_language.ctrl.title">
+				<source>Language Configuration for a Site</source>
+			</trans-unit>
+			<trans-unit id="sys_site_language.language">
+				<source>Language</source>
+			</trans-unit>
+			<trans-unit id="sys_site_language.title">
+				<source>Language title (e.g. "English")</source>
+			</trans-unit>
+			<trans-unit id="sys_site_language.navigationTitle">
+				<source>Navigation title (e.g. "English", "Deutsch", "Français")</source>
+			</trans-unit>
+			<trans-unit id="sys_site_language.base">
+				<source>Entry point (either https://www.mydomain.fr/ or /fr/)</source>
+			</trans-unit>
+			<trans-unit id="sys_site_language.locale">
+				<source>Language locale</source>
+			</trans-unit>
+			<trans-unit id="sys_site_language.iso-639-1">
+				<source>Two letter ISO code</source>
+			</trans-unit>
+			<trans-unit id="sys_site_language.hreflang">
+				<source>Language tag defined by RFC 1766 / 3066 for "lang" and "hreflang" attributes</source>
+			</trans-unit>
+			<trans-unit id="sys_site_language.direction">
+				<source>Language direction for "dir" attribute</source>
+			</trans-unit>
+			<trans-unit id="sys_site_language.typo3Language">
+				<source>Language key for XLF files</source>
+			</trans-unit>
+			<trans-unit id="sys_site_language.flag">
+				<source>Select flag icon</source>
+			</trans-unit>
+			<trans-unit id="sys_site_language.fallbackType">
+				<source>Fallback type</source>
+			</trans-unit>
+			<trans-unit id="sys_site_language.fallbacks">
+				<source>Fallback to other language(s) - order is important!</source>
+			</trans-unit>
+
+			<trans-unit id="sys_site_errorhandling.ctrl.title">
+				<source>Error Handling</source>
+			</trans-unit>
+			<trans-unit id="sys_site_errorhandling.tab.rootpaths">
+				<source>Root Paths (optional)</source>
+			</trans-unit>
+			<trans-unit id="sys_site_errorhandling.errorCode">
+				<source>Error Status Code</source>
+			</trans-unit>
+			<trans-unit id="sys_site_errorhandling.errorCode.404">
+				<source>404 (Page not found)</source>
+			</trans-unit>
+			<trans-unit id="sys_site_errorhandling.errorCode.403">
+				<source>403 (Forbidden)</source>
+			</trans-unit>
+			<trans-unit id="sys_site_errorhandling.errorCode.401">
+				<source>401 (Unauthorized)</source>
+			</trans-unit>
+			<trans-unit id="sys_site_errorhandling.errorCode.500">
+				<source>500 (Internal Server Error)</source>
+			</trans-unit>
+			<trans-unit id="sys_site_errorhandling.errorCode.503">
+				<source>503 (Service Unavailable)</source>
+			</trans-unit>
+			<trans-unit id="sys_site_errorhandling.errorCode.0">
+				<source>any error not defined otherwise</source>
+			</trans-unit>
+			<trans-unit id="sys_site_errorhandling.errorHandler">
+				<source>How to handle errors</source>
+			</trans-unit>
+			<trans-unit id="sys_site_errorhandling.errorFluidTemplate">
+				<source>Fluid Template File</source>
+			</trans-unit>
+			<trans-unit id="sys_site_errorhandling.errorFluidTemplatesRootPath">
+				<source>Fluid Templates Root Path</source>
+			</trans-unit>
+			<trans-unit id="sys_site_errorhandling.errorFluidLayoutsRootPath">
+				<source>Fluid Layouts Root Path</source>
+			</trans-unit>
+			<trans-unit id="sys_site_errorhandling.errorFluidPartialsRootPath">
+				<source>Fluid Partials Root Path</source>
+			</trans-unit>
+			<trans-unit id="sys_site_errorhandling.errorContentSource">
+				<source>Show Content From Page</source>
+			</trans-unit>
+			<trans-unit id="sys_site_errorhandling.errorPhpClassFQCN">
+				<source>ErrorHandler Class Target (FQCN)</source>
+			</trans-unit>
+		</body>
+	</file>
+</xliff>
diff --git a/typo3/sysext/backend/Resources/Private/Language/siteconfiguration_fieldinformation.xlf b/typo3/sysext/backend/Resources/Private/Language/siteconfiguration_fieldinformation.xlf
new file mode 100644
index 0000000000000000000000000000000000000000..5051ff417db107171e2ae1a16caa48dc55f1fded
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Private/Language/siteconfiguration_fieldinformation.xlf
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xliff version="1.0" xmlns:t3="http://typo3.org/schemas/xliff">
+	<file t3:id="1522923345" source-language="en" datatype="plaintext" original="messages" date="2015-01-02T11:16:11Z" product-name="backend">
+		<header/>
+		<body>
+			<trans-unit id="sys_site.identifier">
+				<source>This name will be used to create the configuration directory. Mind the recommendations for directory names (only a-z,0-9,_,-) and make it unique.</source>
+			</trans-unit>
+			<trans-unit id="sys_site.base">
+				<source>Main URL to call the frontend in default language.</source>
+			</trans-unit>
+			<trans-unit id="sys_site_language.base">
+				<source>use / to use keep the main URL as configured at field Entry Point. Add language specific suffixes to use those, or configure complete URLs for independent domains.</source>
+			</trans-unit>
+			<trans-unit id="sys_site_language.locale">
+				<source>should be something like de_DE or en_EN.UTF8</source>
+			</trans-unit>
+			<trans-unit id="sys_site_language.typo3Language">
+				<source>Select the language to be used from translation files. Keep default if no translation files are available.</source>
+			</trans-unit>
+			<trans-unit id="sys_site_errorhandling.errorCode">
+				<source>make sure to have at least 0 (not defined otherwise) configured in order to serve helpful error messages to your visitors.</source>
+			</trans-unit>
+			<trans-unit id="sys_site_errorhandling.errorFluidTemplate">
+				<source>absolute or relative path (from site root) to fluid template file</source>
+			</trans-unit>
+			<trans-unit id="sys_site_errorhandling.errorPhpClassFQCN">
+				<source>PHP class full qualified name that serves the error page.</source>
+			</trans-unit>
+		</body>
+	</file>
+</xliff>
diff --git a/typo3/sysext/backend/Resources/Private/Templates/SiteConfiguration/Edit.html b/typo3/sysext/backend/Resources/Private/Templates/SiteConfiguration/Edit.html
new file mode 100644
index 0000000000000000000000000000000000000000..eca5d6776513e3bd5eba4539d7fe93a36eec0fb9
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Private/Templates/SiteConfiguration/Edit.html
@@ -0,0 +1,18 @@
+<f:be.pageRenderer includeRequireJsModules="{0: 'TYPO3/CMS/Backend/SiteInlineActions'}" />
+<form
+    action="{f:be.uri(route:'site_configuration', parameters:{action: 'save'})}"
+    method="post"
+    enctype="multipart/form-data"
+    name="editform"
+    id="siteConfigurationController"
+    onsubmit="TBE_EDITOR.checkAndDoSubmit(1); return false;"
+>
+    {formEngineHtml -> f:format.raw()}
+
+    <input type="hidden" name="returnUrl" value="{returnUrl -> f:format.raw()}" />
+    <input type="hidden" name="closeDoc" value="0" />
+    <input type="hidden" name="doSave" value="0" />
+    <input type="hidden" name="rootPageId" value="{rootPageId}" />
+
+    {formEngineFooter -> f:format.raw()}
+</form>
\ No newline at end of file
diff --git a/typo3/sysext/backend/Resources/Private/Templates/SiteConfiguration/Overview.html b/typo3/sysext/backend/Resources/Private/Templates/SiteConfiguration/Overview.html
new file mode 100644
index 0000000000000000000000000000000000000000..ebf3726439a2e28296613543523cad458c4a429a
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Private/Templates/SiteConfiguration/Overview.html
@@ -0,0 +1,79 @@
+<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers" data-namespace-typo3-fluid="true">
+<h1><f:translate key="LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:overview.title" /></h1>
+
+<div class="table-fit">
+    <table class="table table-striped table-hover table-condensed">
+        <thead>
+        <tr>
+            <th><f:translate key="LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:overview.site" /></th>
+            <th><f:translate key="LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:overview.configuration" /></th>
+            <th><f:translate key="LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:overview.baseUrl" /></th>
+            <th>&nbsp;</th>
+        </tr>
+        </thead>
+        <tbody>
+        <f:for each="{pages}" as="page">
+            <tr>
+                <td nowrap valign="top">
+                    <f:for each="{page.rootline}" as="rootLinePage" iteration="i">
+                        <f:if condition="{rootLinePage.uid} == {page.uid}">
+                            <f:then>
+                                <a href="#" class="t3js-contextmenutrigger" data-table="pages" data-uid="{rootLinePage.uid}">
+                                    <core:iconForRecord table="pages" row="{rootLinePage}" />
+                                </a> {rootLinePage.title} [ID: {page.uid}]
+                            </f:then>
+                            <f:else>
+                                <core:iconForRecord table="pages" row="{rootLinePage}" />
+                                {rootLinePage.title}<br>
+                            </f:else>
+                        </f:if>
+                    </f:for>
+                </td>
+                <td>
+                    <f:if condition="{page.siteIdentifier}">
+                        <f:then>
+                            <code>{page.siteIdentifier}</code>
+                        </f:then>
+                        <f:else>
+                            <div>
+                                <f:be.link route="site_configuration" parameters="{action: 'edit', pageUid: page.uid}" title="Create configuration" class="btn btn-primary">
+                                    <f:translate key="LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration.xlf:overview.addSiteConfiguration" />
+                                </f:be.link>
+                            </div>
+                        </f:else>
+                    </f:if>
+                </td>
+                <td>
+                    <f:if condition="{page.siteConfiguration}">
+                        <table class="table table-striped table-no-borders">
+                            <tr>
+                                <th>Language Name</th>
+                                <th>Full URL Prefix</th>
+                            </tr>
+                        <f:for each="{page.siteConfiguration.languages}" as="siteLanguage">
+                            <tr>
+                                <td><core:icon identifier="flags-{siteLanguage.flagIdentifier}" /> {siteLanguage.title}</td>
+                                <td><a href="{siteLanguage.base}" target="_blank">{siteLanguage.base}</a></td>
+                            </tr>
+                        </f:for>
+                        </table>
+                    </f:if>
+                </td>
+                <td>
+                    <div class="btn-group">
+                        <f:if condition="{page.siteIdentifier}">
+                            <f:be.link route="site_configuration" parameters="{action: 'edit', site: page.siteIdentifier}" title="Edit" class="btn btn-default">
+                                <core:icon identifier="actions-open" />
+                            </f:be.link>
+                            <f:be.link route="site_configuration" parameters="{action: 'delete', site:page.siteIdentifier}" title="Delete configuration" class="btn btn-default">
+                                <core:icon identifier="actions-delete" />
+                            </f:be.link>
+                        </f:if>
+                    </div>
+                </td>
+            </tr>
+        </f:for>
+        </tbody>
+    </table>
+    </div>
+</html>
diff --git a/typo3/sysext/backend/Resources/Public/Icons/module-contentelements.svg b/typo3/sysext/backend/Resources/Public/Icons/module-contentelements.svg
new file mode 100644
index 0000000000000000000000000000000000000000..342c843d5d79fcaad5bf583b3d28032272a96a66
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Public/Icons/module-contentelements.svg
@@ -0,0 +1 @@
+<!-- Copyright © 2015 MODULUS Sp. z o. o. / FUTURAMO™ --><svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="64px" height="64px" viewBox="0 0 64 64"><rect x="0" y="0" width="64" height="64" rx="0" ry="0" fill="#696DBB"></rect><path transform="translate(16, 16)" fill="#FFFFFF" d="M16,16c0-1.104,0.896-2,2-2c1.104,0,2,0.896,2,2c0,1.104-0.896,2-2,2C16.896,18,16,17.104,16,16z M16,20 l-4.286,2l-5.143-6L4,20.375V26h18L16,20z M6,4h26v20h-6v6H0V10h6V4z M24,12H2v16h22V12z M8,10h18v12h4V6H8V10z"></path></svg>
\ No newline at end of file
diff --git a/typo3/sysext/backend/Resources/Public/Icons/module-sites.svg b/typo3/sysext/backend/Resources/Public/Icons/module-sites.svg
new file mode 100644
index 0000000000000000000000000000000000000000..fe87ff9fcfdee5efa69f3015452cc966298bf8fe
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Public/Icons/module-sites.svg
@@ -0,0 +1 @@
+<!-- Copyright © 2015 MODULUS Sp. z o. o. / FUTURAMO™ --><svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="64px" height="64px" viewBox="0 0 64 64"><rect x="0" y="0" width="64" height="64" rx="0" ry="0" fill="#69BBB5"></rect><path transform="translate(16, 16)" fill="#FFFFFF" d="M31.634,6.064L30.109,5.81c-0.066-0.284-0.155-0.559-0.265-0.823c-0.11-0.264-0.241-0.517-0.391-0.757 l0.9-1.259v0l0.215-0.301l-0.157-0.157l-0.105-0.105l0,0l-0.713-0.714l0,0l-0.262-0.262L29.15,1.561h0l-1.38,0.986 c-0.481-0.3-1.012-0.524-1.58-0.656l-0.254-1.525l0,0L25.875,0h-0.222h-0.148h-1.009h-0.148h-0.222l-0.036,0.219l0,0L23.81,1.891 c-0.568,0.132-1.1,0.356-1.581,0.656l-1.259-0.9l0,0l-0.302-0.216l-0.157,0.157l0,0l-0.679,0.68l-0.139,0.139l0,0l-0.105,0.105 l-0.157,0.157L19.56,2.85l0,0l0.985,1.38c-0.299,0.481-0.523,1.012-0.655,1.58l-1.671,0.278l0,0L18,6.125l0,0.222l0,0l0.001,1.305v0 l0,0.222l0.365,0.061l0,0l0.819,0.136l0.705,0.117c0.066,0.284,0.155,0.559,0.265,0.823c0.11,0.264,0.241,0.517,0.391,0.757 l-0.986,1.38l0,0l-0.129,0.181l0.157,0.157c0,0,0,0,0,0l0.924,0.922h0l0.157,0.157l0.301-0.215h0l1.259-0.899 c0.481,0.3,1.012,0.524,1.58,0.656l0.278,1.671l0,0L24.125,14h0.222h0.148h1.009h0.148h0.222l0.061-0.365l0,0l0.254-1.525 c0.569-0.132,1.1-0.356,1.581-0.656l0.998,0.713l0.261,0.187h0l0.302,0.216l0.262-0.262l0.713-0.713l0.262-0.262l-0.215-0.302l0,0 l-0.9-1.26c0.15-0.24,0.281-0.493,0.391-0.757c0.11-0.264,0.199-0.539,0.265-0.823l1.525-0.254l0,0L32,7.875V7.653V7.504V6.496 V6.347V6.125L31.634,6.064L31.634,6.064z M25,10c-1.657,0-3-1.343-3-3s1.343-3,3-3s3,1.343,3,3S26.657,10,25,10z M30,16h2v8H18v6h8 v2H6v-2h8v-6H0V0h16v2H2v20h28V16z"></path></svg>
\ No newline at end of file
diff --git a/typo3/sysext/backend/Resources/Public/Icons/module-templates.svg b/typo3/sysext/backend/Resources/Public/Icons/module-templates.svg
new file mode 100644
index 0000000000000000000000000000000000000000..a8156e7e687ffdae79e57a82106af2f6020d4a16
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Public/Icons/module-templates.svg
@@ -0,0 +1 @@
+<!-- Copyright © 2015 MODULUS Sp. z o. o. / FUTURAMO™ --><svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="64px" height="64px" viewBox="0 0 64 64"><rect x="0" y="0" width="64" height="64" rx="0" ry="0" fill="#B069BB"></rect><path transform="translate(16, 16)" fill="#FFFFFF" d="M28,4v4H4V4H28 M30,2H2v8h28V2L30,2z M28,14v14h-4V14H28 M30,12h-8v18h8V12L30,12z M18,14v14H4V14H18 M20,12 H2v18h18V12L20,12z"></path></svg>
\ No newline at end of file
diff --git a/typo3/sysext/backend/Resources/Public/Icons/module-urls.svg b/typo3/sysext/backend/Resources/Public/Icons/module-urls.svg
new file mode 100644
index 0000000000000000000000000000000000000000..b0abf121da52cd07b093919a7bbcba0f5c5e7a56
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Public/Icons/module-urls.svg
@@ -0,0 +1 @@
+<!-- Copyright © 2015 MODULUS Sp. z o. o. / FUTURAMO™ --><svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="64px" height="64px" viewBox="0 0 64 64"><rect x="0" y="0" width="64" height="64" rx="0" ry="0" fill="#69bb7d"></rect><path transform="translate(16, 16)" fill="#FFFFFF" d="M20,26v4h10V20H20v4h-8V10h8v4h10V4H20v4H10v8H0l0,2h10v8H20z M22,6h6v6h-6V6z M22,22h6v6h-6V22z"></path></svg>
\ No newline at end of file
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/SiteInlineActions.js b/typo3/sysext/backend/Resources/Public/JavaScript/SiteInlineActions.js
new file mode 100644
index 0000000000000000000000000000000000000000..08db9e8947bed55bda66df1e3de3e30a089fe078
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/SiteInlineActions.js
@@ -0,0 +1,21 @@
+/*
+ * 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!
+ */
+
+// Site configuration backend module FormEngine inline:
+// Override inline 'create' and 'details' route to point to SiteInlineAjaxController
+require(['jquery'], function($) {
+  $(function() {
+    TYPO3.inline.addMethod('create', 'site_configuration_inline_create');
+    TYPO3.inline.addMethod('details', 'site_configuration_inline_details');
+  });
+});
\ No newline at end of file
diff --git a/typo3/sysext/backend/Tests/Unit/Form/FormDataGroup/SiteConfigurationTest.php b/typo3/sysext/backend/Tests/Unit/Form/FormDataGroup/SiteConfigurationTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..079b94d815e6be1df8bc6e7c083694221cf457fe
--- /dev/null
+++ b/typo3/sysext/backend/Tests/Unit/Form/FormDataGroup/SiteConfigurationTest.php
@@ -0,0 +1,103 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Backend\Tests\Unit\Form\FormDataGroup;
+
+/*
+ * 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!
+ */
+
+use Prophecy\Argument;
+use Prophecy\Prophecy\ObjectProphecy;
+use TYPO3\CMS\Backend\Form\FormDataGroup\SiteConfigurationDataGroup;
+use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
+use TYPO3\CMS\Core\Service\DependencyOrderingService;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+/**
+ * Test case
+ */
+class SiteConfigurationTest extends UnitTestCase
+{
+    /**
+     * @var SiteConfigurationDataGroup
+     */
+    protected $subject;
+
+    protected function setUp()
+    {
+        $this->subject = new SiteConfigurationDataGroup();
+    }
+
+    /**
+     * @test
+     */
+    public function compileReturnsIncomingData()
+    {
+        /** @var DependencyOrderingService|ObjectProphecy $orderingServiceProphecy */
+        $orderingServiceProphecy = $this->prophesize(DependencyOrderingService::class);
+        GeneralUtility::addInstance(DependencyOrderingService::class, $orderingServiceProphecy->reveal());
+        $orderingServiceProphecy->orderByDependencies(Argument::cetera())->willReturnArgument(0);
+
+        $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['formDataGroup']['siteConfiguration'] = [];
+
+        $input = ['foo'];
+
+        $this->assertEquals($input, $this->subject->compile($input));
+    }
+
+    /**
+     * @test
+     */
+    public function compileReturnsResultChangedByDataProvider()
+    {
+        /** @var DependencyOrderingService|ObjectProphecy $orderingServiceProphecy */
+        $orderingServiceProphecy = $this->prophesize(DependencyOrderingService::class);
+        GeneralUtility::addInstance(DependencyOrderingService::class, $orderingServiceProphecy->reveal());
+        $orderingServiceProphecy->orderByDependencies(Argument::cetera())->willReturnArgument(0);
+
+        /** @var FormDataProviderInterface|ObjectProphecy $formDataProviderProphecy */
+        $formDataProviderProphecy = $this->prophesize(FormDataProviderInterface::class);
+        $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['formDataGroup']['siteConfiguration'] = [
+            FormDataProviderInterface::class => [],
+        ];
+        GeneralUtility::addInstance(FormDataProviderInterface::class, $formDataProviderProphecy->reveal());
+        $providerResult = ['foo'];
+        $formDataProviderProphecy->addData(Argument::cetera())->shouldBeCalled()->willReturn($providerResult);
+
+        $this->assertEquals($providerResult, $this->subject->compile([]));
+    }
+
+    /**
+     * @test
+     */
+    public function compileThrowsExceptionIfDataProviderDoesNotImplementInterface()
+    {
+        /** @var DependencyOrderingService|ObjectProphecy $orderingServiceProphecy */
+        $orderingServiceProphecy = $this->prophesize(DependencyOrderingService::class);
+        GeneralUtility::addInstance(DependencyOrderingService::class, $orderingServiceProphecy->reveal());
+        $orderingServiceProphecy->orderByDependencies(Argument::cetera())->willReturnArgument(0);
+
+        /** @var FormDataProviderInterface|ObjectProphecy $formDataProviderProphecy */
+        $formDataProviderProphecy = $this->prophesize(\stdClass::class);
+        $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['formDataGroup']['siteConfiguration'] = [
+            \stdClass::class => [],
+        ];
+        GeneralUtility::addInstance(\stdClass::class, $formDataProviderProphecy->reveal());
+
+        $this->expectException(\UnexpectedValueException::class);
+        $this->expectExceptionCode(1485299408);
+
+        $this->subject->compile([]);
+    }
+}
diff --git a/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/SiteDatabaseEditRowTest.php b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/SiteDatabaseEditRowTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..b01f82973bdaec15ac1e1815e6b126dd775bb797
--- /dev/null
+++ b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/SiteDatabaseEditRowTest.php
@@ -0,0 +1,196 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Backend\Tests\Unit\Form\FormDataProvider;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Backend\Form\FormDataProvider\SiteDatabaseEditRow;
+use TYPO3\CMS\Core\Site\Entity\Site;
+use TYPO3\CMS\Core\Site\SiteFinder;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+/**
+ * Test case
+ */
+class SiteDatabaseEditRowTest extends UnitTestCase
+{
+    /**
+     * @test
+     */
+    public function addDataDoesNotChangeResultIfCommandIsNotEdit()
+    {
+        $input = [
+            'command' => 'new',
+            'foo' => 'bar',
+        ];
+        $this->assertSame($input, (new SiteDatabaseEditRow())->addData($input));
+    }
+
+    /**
+     * @test
+     */
+    public function addDataDoesNotChangeResultIfDatabaseRowIsNotEmpty()
+    {
+        $input = [
+            'command' => 'edit',
+            'databaseRow' => [
+                'foo' => 'bar',
+            ]
+        ];
+        $this->assertSame($input, (new SiteDatabaseEditRow())->addData($input));
+    }
+
+    /**
+     * @test
+     */
+    public function addDataThrowsExceptionIfTableNameIsNotExpected()
+    {
+        $input = [
+            'command' => 'edit',
+            'tableName' => 'foo',
+        ];
+        $this->expectException(\RuntimeException::class);
+        $this->expectExceptionCode(1520886234);
+        $siteFinderProphecy = $this->prophesize(SiteFinder::class);
+        GeneralUtility::addInstance(SiteFinder::class, $siteFinderProphecy->reveal());
+        (new SiteDatabaseEditRow())->addData($input);
+    }
+
+    /**
+     * @test
+     */
+    public function addDataSetsDataForSysSite()
+    {
+        $input = [
+            'command' => 'edit',
+            'tableName' => 'sys_site',
+            'vanillaUid' => 23,
+            'customData' => [
+                'siteIdentifier' => 'main',
+            ]
+        ];
+        $rowData = [
+            'foo' => 'bar',
+            'rootPageId' => 42,
+            'someArray' => [
+                'foo' => 'bar',
+            ]
+        ];
+        $siteFinderProphecy = $this->prophesize(SiteFinder::class);
+        GeneralUtility::addInstance(SiteFinder::class, $siteFinderProphecy->reveal());
+        $siteProphecy = $this->prophesize(Site::class);
+        $siteFinderProphecy->getSiteByRootPageId(23)->willReturn($siteProphecy->reveal());
+        $siteProphecy->getConfiguration()->willReturn($rowData);
+
+        $expected = $input;
+        $expected['databaseRow'] = [
+            'uid' => 42,
+            'identifier' => 'main',
+            'rootPageId' => 42,
+            'pid' => 0,
+            'foo' => 'bar',
+        ];
+
+        $this->assertEquals($expected, (new SiteDatabaseEditRow())->addData($input));
+    }
+
+    /**
+     * @test
+     */
+    public function addDataThrowsExceptionWithInvalidErrorHandling()
+    {
+        $input = [
+            'command' => 'edit',
+            'tableName' => 'sys_site_errorhandling',
+            'vanillaUid' => 23,
+            'inlineTopMostParentUid' => 5,
+            'inlineParentFieldName' => 'invalid',
+        ];
+        $rowData = [
+            'foo' => 'bar',
+        ];
+        $siteFinderProphecy = $this->prophesize(SiteFinder::class);
+        GeneralUtility::addInstance(SiteFinder::class, $siteFinderProphecy->reveal());
+        $siteProphecy = $this->prophesize(Site::class);
+        $siteFinderProphecy->getSiteByRootPageId(5)->willReturn($siteProphecy->reveal());
+        $siteProphecy->getConfiguration()->willReturn($rowData);
+
+        $this->expectException(\RuntimeException::class);
+        $this->expectExceptionCode(1520886092);
+        (new SiteDatabaseEditRow())->addData($input);
+    }
+
+    /**
+     * @test
+     */
+    public function addDataThrowsExceptionWithInvalidLanguage()
+    {
+        $input = [
+            'command' => 'edit',
+            'tableName' => 'sys_site_language',
+            'vanillaUid' => 23,
+            'inlineTopMostParentUid' => 5,
+            'inlineParentFieldName' => 'invalid',
+        ];
+        $rowData = [
+            'foo' => 'bar',
+        ];
+        $siteFinderProphecy = $this->prophesize(SiteFinder::class);
+        GeneralUtility::addInstance(SiteFinder::class, $siteFinderProphecy->reveal());
+        $siteProphecy = $this->prophesize(Site::class);
+        $siteFinderProphecy->getSiteByRootPageId(5)->willReturn($siteProphecy->reveal());
+        $siteProphecy->getConfiguration()->willReturn($rowData);
+
+        $this->expectException(\RuntimeException::class);
+        $this->expectExceptionCode(1520886092);
+        (new SiteDatabaseEditRow())->addData($input);
+    }
+
+    /**
+     * @test
+     */
+    public function addDataAddLanguageRow()
+    {
+        $input = [
+            'command' => 'edit',
+            'tableName' => 'sys_site_language',
+            'vanillaUid' => 23,
+            'inlineTopMostParentUid' => 5,
+            'inlineParentFieldName' => 'languages',
+        ];
+        $rowData = [
+            'languages' => [
+                23 => [
+                    'foo' => 'bar',
+                ],
+            ],
+        ];
+        $siteFinderProphecy = $this->prophesize(SiteFinder::class);
+        GeneralUtility::addInstance(SiteFinder::class, $siteFinderProphecy->reveal());
+        $siteProphecy = $this->prophesize(Site::class);
+        $siteFinderProphecy->getSiteByRootPageId(5)->willReturn($siteProphecy->reveal());
+        $siteProphecy->getConfiguration()->willReturn($rowData);
+
+        $expected = $input;
+        $expected['databaseRow'] = [
+            'foo' => 'bar',
+            'uid' => 23,
+            'pid' => 0,
+        ];
+
+        $this->assertEquals($expected, (new SiteDatabaseEditRow())->addData($input));
+    }
+}
diff --git a/typo3/sysext/backend/ext_localconf.php b/typo3/sysext/backend/ext_localconf.php
index 9b3f52749249fb9f8550deda0008d314ef56921b..5c9d69973f56ecf061208201599511713f77bc1d 100644
--- a/typo3/sysext/backend/ext_localconf.php
+++ b/typo3/sysext/backend/ext_localconf.php
@@ -43,3 +43,10 @@ $GLOBALS['TYPO3_CONF_VARS']['SYS']['livesearch']['page'] = 'pages';
 
 // Register BackendLayoutDataProvider for PageTs
 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['BackendLayoutDataProvider']['pagets'] = \TYPO3\CMS\Backend\Provider\PageTsBackendLayoutDataProvider::class;
+
+// Register fieldInformation Provider for site configuration module
+$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry'][1522919823] = [
+    'nodeName' => 'SiteConfigurationModuleFieldInformation',
+    'priority' => '70',
+    'class' => \TYPO3\CMS\Backend\Form\FieldInformation\SiteConfiguration::class
+];
diff --git a/typo3/sysext/backend/ext_tables.php b/typo3/sysext/backend/ext_tables.php
index c23146803e9c781bb2cca5587949165b6ca33136..ac86abf7869d1dbd066b94cbe72e2c1e96093af6 100644
--- a/typo3/sysext/backend/ext_tables.php
+++ b/typo3/sysext/backend/ext_tables.php
@@ -23,6 +23,20 @@ $GLOBALS['TBE_STYLES']['skins']['backend'] = [
     ]
 );
 
+\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addModule(
+    'site',
+    'configuration',
+    'top',
+    '',
+    [
+        'routeTarget' => \TYPO3\CMS\Backend\Controller\SiteConfigurationController::class . '::handleRequest',
+        'access' => 'admin',
+        'name' => 'site_configuration',
+        'icon' => 'EXT:backend/Resources/Public/Icons/module-sites.svg',
+        'labels' => 'LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_module.xlf'
+    ]
+);
+
 // "Sort sub pages" csh
 \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addLLrefForTCAdescr(
     'pages_sort',
diff --git a/typo3/sysext/core/Classes/Configuration/SiteConfiguration.php b/typo3/sysext/core/Classes/Configuration/SiteConfiguration.php
new file mode 100644
index 0000000000000000000000000000000000000000..f444f40f5ae1cf6b6e0fc4e7d68df044bb98e934
--- /dev/null
+++ b/typo3/sysext/core/Classes/Configuration/SiteConfiguration.php
@@ -0,0 +1,170 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Core\Configuration;
+
+/*
+ * 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!
+ */
+
+use Symfony\Component\Finder\Finder;
+use Symfony\Component\Yaml\Yaml;
+use TYPO3\CMS\Core\Cache\CacheManager;
+use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
+use TYPO3\CMS\Core\Configuration\Loader\YamlFileLoader;
+use TYPO3\CMS\Core\Exception\SiteNotFoundException;
+use TYPO3\CMS\Core\Site\Entity\Site;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Responsibility: Handles the format of the configuration (currently yaml), and the location of the file system folder
+ *
+ * Reads all available site configuration options, and puts them into Site objects.
+ *
+ * @internal
+ */
+class SiteConfiguration
+{
+    /**
+     * @var string
+     */
+    protected $configPath;
+
+    /**
+     * Config yaml file name.
+     *
+     * @internal
+     * @var string
+     */
+    protected $configFileName = 'config.yaml';
+
+    /**
+     * Identifier to store all configuration data in cache_core cache.
+     *
+     * @internal
+     * @var string
+     */
+    protected $cacheIdentifier = 'site-configuration';
+
+    /**
+     * @param string $configPath
+     */
+    public function __construct(string $configPath)
+    {
+        $this->configPath = $configPath;
+    }
+
+    /**
+     * Return all site objects which have been found in the filesystem.
+     *
+     * @return Site[]
+     */
+    public function resolveAllExistingSites(): array
+    {
+        // Check if the data is already cached
+        if ($siteConfiguration = $this->getCache()->get($this->cacheIdentifier)) {
+            $siteConfiguration = json_decode($siteConfiguration, true);
+        }
+
+        // Nothing in the cache (or no site found)
+        if (empty($siteConfiguration)) {
+            $finder = new Finder();
+            try {
+                $finder->files()->depth(0)->name($this->configFileName)->in($this->configPath . '/*');
+            } catch (\InvalidArgumentException $e) {
+                // Directory $this->configPath does not exist yet
+                $finder = [];
+            }
+            $loader = GeneralUtility::makeInstance(YamlFileLoader::class);
+            $siteConfiguration = [];
+            foreach ($finder as $fileInfo) {
+                $configuration = $loader->load((string)$fileInfo);
+                $identifier = basename($fileInfo->getPath());
+                $siteConfiguration[$identifier] = $configuration;
+            }
+            $this->getCache()->set($this->cacheIdentifier, json_encode($siteConfiguration));
+        }
+        $sites = [];
+        foreach ($siteConfiguration ?? [] as $identifier => $configuration) {
+            $rootPageId = (int)$configuration['site']['rootPageId'] ?? 0;
+            if ($rootPageId > 0) {
+                $sites[$identifier] = GeneralUtility::makeInstance(Site::class, $identifier, $rootPageId, $configuration['site']);
+            }
+        }
+        return $sites;
+    }
+
+    /**
+     * Add or update a site configuration
+     *
+     * @param string $siteIdentifier
+     * @param array $configuration
+     * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
+     */
+    public function write(string $siteIdentifier, array $configuration)
+    {
+        $fileName = $this->configPath . '/' . $siteIdentifier . '/' . $this->configFileName;
+        if (!file_exists($fileName)) {
+            GeneralUtility::mkdir_deep($this->configPath . '/' . $siteIdentifier);
+        }
+        $yamlFileContents = Yaml::dump($configuration, 99, 2);
+        GeneralUtility::writeFile($fileName, $yamlFileContents);
+        $this->getCache()->remove($this->cacheIdentifier);
+    }
+
+    /**
+     * Renames a site identifier (and moves the folder)
+     *
+     * @param string $currentIdentifier
+     * @param string $newIdentifier
+     * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
+     */
+    public function rename(string $currentIdentifier, string $newIdentifier)
+    {
+        $result = rename($this->configPath . '/' . $currentIdentifier, $this->configPath . '/' . $newIdentifier);
+        if (!$result) {
+            throw new \RuntimeException('Unable to rename folder sites/' . $currentIdentifier, 1522491300);
+        }
+        $this->getCache()->remove($this->cacheIdentifier);
+    }
+
+    /**
+     * Removes the config.yaml file of a site configuration.
+     * Also clears the cache.
+     *
+     * @param string $siteIdentifier
+     * @throws SiteNotFoundException
+     */
+    public function delete(string $siteIdentifier)
+    {
+        $sites = $this->resolveAllExistingSites();
+        if (!isset($sites[$siteIdentifier])) {
+            throw new SiteNotFoundException('Site configuration named ' . $siteIdentifier . ' not found.', 1522866183);
+        }
+        $fileName = $this->configPath . '/' . $siteIdentifier . '/' . $this->configFileName;
+        if (!file_exists($fileName)) {
+            throw new SiteNotFoundException('Site configuration file ' . $this->configFileName . ' within the site ' . $siteIdentifier . ' not found.', 1522866184);
+        }
+        @unlink($fileName);
+        $this->getCache()->remove($this->cacheIdentifier);
+    }
+
+    /**
+     * Short-hand function for the cache
+     *
+     * @return FrontendInterface
+     * @throws \TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException
+     */
+    protected function getCache(): FrontendInterface
+    {
+        return GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_core');
+    }
+}
diff --git a/typo3/sysext/core/Classes/Exception/SiteNotFoundException.php b/typo3/sysext/core/Classes/Exception/SiteNotFoundException.php
new file mode 100644
index 0000000000000000000000000000000000000000..42ae05205cf66412f7de69ea97016f9e7cd7566d
--- /dev/null
+++ b/typo3/sysext/core/Classes/Exception/SiteNotFoundException.php
@@ -0,0 +1,26 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Core\Exception;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Core\Exception;
+
+/**
+ * Exception thrown if site configuration or incoming data is invalid
+ */
+class SiteNotFoundException extends Exception
+{
+}
diff --git a/typo3/sysext/core/Classes/Site/Entity/Site.php b/typo3/sysext/core/Classes/Site/Entity/Site.php
new file mode 100644
index 0000000000000000000000000000000000000000..908009a9d9298f9c0581aada737983c3bd475f90
--- /dev/null
+++ b/typo3/sysext/core/Classes/Site/Entity/Site.php
@@ -0,0 +1,223 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Core\Site\Entity;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Frontend\PageErrorHandler\FluidPageErrorHandler;
+use TYPO3\CMS\Frontend\PageErrorHandler\PageContentErrorHandler;
+use TYPO3\CMS\Frontend\PageErrorHandler\PageErrorHandlerInterface;
+
+/**
+ * Entity representing a single site with available languages
+ */
+class Site
+{
+    const ERRORHANDLER_TYPE_PAGE = 'Page';
+    const ERRORHANDLER_TYPE_FLUID = 'Fluid';
+    const ERRORHANDLER_TYPE_PHP = 'PHP';
+
+    /**
+     * @var string
+     */
+    protected $identifier;
+
+    /**
+     * @var string
+     */
+    protected $base;
+
+    /**
+     * @var int
+     */
+    protected $rootPageId;
+
+    /**
+     * Any attributes for this site
+     * @var array
+     */
+    protected $configuration;
+
+    /**
+     * @var SiteLanguage[]
+     */
+    protected $languages;
+
+    /**
+     * @var array
+     */
+    protected $errorHandlers;
+
+    /**
+     * Sets up a site object, and its languages and error handlers
+     *
+     * @param string $identifier
+     * @param int $rootPageId
+     * @param array $configuration
+     */
+    public function __construct(string $identifier, int $rootPageId, array $configuration)
+    {
+        $this->identifier = $identifier;
+        $this->rootPageId = $rootPageId;
+        $this->configuration = $configuration;
+        $configuration['languages'] = $configuration['languages'] ?: [0 => [
+            'languageId' => 0,
+            'title' => 'Default',
+            'navigationTitle' => '',
+            'typo3Language' => 'default',
+            'flag' => 'us',
+            'locale' => 'en_US.UTF-8',
+            'iso-639-1' => 'en',
+            'hreflang' => 'en-US',
+            'direction' => '',
+        ]];
+        $this->base = $configuration['base'] ?? '';
+        foreach ($configuration['languages'] as $languageConfiguration) {
+            $languageUid = (int)$languageConfiguration['languageId'];
+            $base = '/';
+            if (!empty($languageConfiguration['base'])) {
+                $base = $languageConfiguration['base'];
+            }
+            $baseParts = parse_url($base);
+            if (empty($baseParts['scheme'])) {
+                $base = rtrim($this->base, '/') . '/' . ltrim($base, '/');
+            }
+            $this->languages[$languageUid] = new SiteLanguage(
+                $this,
+                $languageUid,
+                $languageConfiguration['locale'],
+                $base,
+                $languageConfiguration
+            );
+        }
+        foreach ($configuration['errorHandling'] ?? [] as $errorHandlingConfiguration) {
+            $code = $errorHandlingConfiguration['errorCode'];
+            unset($errorHandlingConfiguration['errorCode']);
+            $this->errorHandlers[(int)$code] = $errorHandlingConfiguration;
+        }
+    }
+
+    /**
+     * Gets the identifier of this site
+     *
+     * @return string
+     */
+    public function getIdentifier(): string
+    {
+        return $this->identifier;
+    }
+
+    /**
+     * Returns the base URL of this site
+     *
+     * @return string
+     */
+    public function getBase(): string
+    {
+        return $this->base;
+    }
+
+    /**
+     * Returns the root page ID of this site
+     *
+     * @return int
+     */
+    public function getRootPageId(): int
+    {
+        return $this->rootPageId;
+    }
+
+    /**
+     * Returns all available langauges of this site
+     *
+     * @return SiteLanguage[]
+     */
+    public function getLanguages(): array
+    {
+        return $this->languages;
+    }
+
+    /**
+     * Returns a language of this site, given by the sys_language_uid
+     *
+     * @param int $languageId
+     * @return SiteLanguage
+     * @throws \InvalidArgumentException
+     */
+    public function getLanguageById(int $languageId): SiteLanguage
+    {
+        if (isset($this->languages[$languageId])) {
+            return $this->languages[$languageId];
+        }
+        throw new \InvalidArgumentException(
+            'Language ' . $languageId . ' does not exist on site ' . $this->identifier . '.',
+            1522960188
+        );
+    }
+
+    /**
+     * Returns a ready-to-use error handler, to be used within the ErrorController
+     *
+     * @param int $type
+     * @return PageErrorHandlerInterface
+     * @throws \RuntimeException
+     */
+    public function getErrorHandler(int $type): PageErrorHandlerInterface
+    {
+        $errorHandler = $this->errorHandlers[$type];
+        switch ($errorHandler['errorHandler']) {
+            case self::ERRORHANDLER_TYPE_FLUID:
+                return new FluidPageErrorHandler($type, $errorHandler);
+            case self::ERRORHANDLER_TYPE_PAGE:
+                return new PageContentErrorHandler($type, $errorHandler);
+            case self::ERRORHANDLER_TYPE_PHP:
+                // Check if the interface is implemented
+                $handler = GeneralUtility::makeInstance($errorHandler['errorPhpClassFQCN'], $type, $errorHandler);
+                if (!($handler instanceof PageErrorHandlerInterface)) {
+                    // throw new exception
+                }
+                return $handler;
+        }
+        throw new \RuntimeException('Not implemented', 1522495914);
+    }
+
+    /**
+     * Returns the whole configuration for this site
+     *
+     * @return array
+     */
+    public function getConfiguration(): array
+    {
+        return $this->configuration;
+    }
+
+    /**
+     * Returns a single configuration attribute
+     *
+     * @param string $attributeName
+     * @return mixed
+     * @throws \InvalidArgumentException
+     */
+    public function getAttribute(string $attributeName)
+    {
+        if (isset($this->configuration[$attributeName])) {
+            return $this->configuration[$attributeName];
+        }
+        throw new \InvalidArgumentException(
+            'Attribute ' . $attributeName . ' does not exist on site ' . $this->identifier . '.',
+            1522495954
+        );
+    }
+}
diff --git a/typo3/sysext/core/Classes/Site/Entity/SiteLanguage.php b/typo3/sysext/core/Classes/Site/Entity/SiteLanguage.php
new file mode 100644
index 0000000000000000000000000000000000000000..2fc49c87b3b5effe62fe72efd32219fd61b21acc
--- /dev/null
+++ b/typo3/sysext/core/Classes/Site/Entity/SiteLanguage.php
@@ -0,0 +1,289 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Core\Site\Entity;
+
+/*
+ * 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!
+ */
+
+/**
+ * Entity representing a sys_sitelanguage configuration of a site object.
+ */
+class SiteLanguage
+{
+    /**
+     * @var Site
+     */
+    protected $site;
+
+    /**
+     * The language mapped to the sys_language DB entry.
+     *
+     * @var int
+     */
+    protected $languageId;
+
+    /**
+     * Locale, like 'de_CH' or 'en_GB'
+     *
+     * @var string
+     */
+    protected $locale;
+
+    /**
+     * The Base URL for this language
+     *
+     * @var string
+     */
+    protected $base;
+
+    /**
+     * Label to be used within TYPO3 to identify the language
+     * @var string
+     */
+    protected $title = 'Default';
+
+    /**
+     * Label to be used within language menus
+     * @var string
+     */
+    protected $navigationTitle = '';
+
+    /**
+     * The flag key (like "gb" or "fr") used to be used in TYPO3's Backend.
+     * @var string
+     */
+    protected $flagIdentifier = 'us';
+
+    /**
+     * The iso code for this language (two letter) ISO-639-1
+     * @var string
+     */
+    protected $twoLetterIsoCode = 'en';
+
+    /**
+     * Language tag for this language defined by RFC 1766 / 3066 for "lang"
+     * and "hreflang" attributes
+     *
+     * @var string
+     */
+    protected $hreflang = 'en-US';
+
+    /**
+     * The direction for this language
+     * @var string
+     */
+    protected $direction = '';
+
+    /**
+     * Prefix for TYPO3's language files
+     * "default" for english, otherwise one of TYPO3's internal language keys.
+     * Previously configured via TypoScript config.language = fr
+     *
+     * @var string
+     */
+    protected $typo3Language = 'default';
+
+    /**
+     * @var string
+     */
+    protected $fallbackType = 'strict';
+
+    /**
+     * @var array
+     */
+    protected $fallbackLanguageIds = [];
+
+    /**
+     * Additional parameters configured for this site language
+     * @var array
+     */
+    protected $attributes = [];
+
+    /**
+     * SiteLanguage constructor.
+     * @param Site $site
+     * @param int $languageId
+     * @param string $locale
+     * @param string $base
+     * @param array $attributes
+     */
+    public function __construct(Site $site, int $languageId, string $locale, string $base, array $attributes)
+    {
+        $this->site = $site;
+        $this->languageId = $languageId;
+        $this->locale = $locale;
+        $this->base = $base;
+        $this->attributes = $attributes;
+        if (!empty($attributes['title'])) {
+            $this->title = $attributes['title'];
+        }
+        if (!empty($attributes['navigationTitle'])) {
+            $this->navigationTitle = $attributes['navigationTitle'];
+        }
+        if (!empty($attributes['flag'])) {
+            $this->flagIdentifier = $attributes['flag'];
+        }
+        if (!empty($attributes['typo3Language'])) {
+            $this->typo3Language = $attributes['typo3Language'];
+        }
+        if (!empty($attributes['iso-639-1'])) {
+            $this->twoLetterIsoCode = $attributes['iso-639-1'];
+        }
+        if (!empty($attributes['hreflang'])) {
+            $this->hreflang = $attributes['hreflang'];
+        }
+        if (!empty($attributes['direction'])) {
+            $this->direction = $attributes['direction'];
+        }
+        if (!empty($attributes['fallbackType'])) {
+            $this->fallbackType = $attributes['fallbackType'];
+        }
+        if (!empty($attributes['fallbacks'])) {
+            $this->fallbackLanguageIds = $attributes['fallbacks'];
+        }
+    }
+
+    /**
+     * Returns the SiteLanguage in an array representation for e.g. the usage
+     * in TypoScript.
+     *
+     * @return array
+     */
+    public function toArray()
+    {
+        return [
+            'languageId' => $this->getLanguageId(),
+            'locale' => $this->getLocale(),
+            'base' => $this->getBase(),
+            'title' => $this->getTitle(),
+            'navigationTitle' => $this->getNavigationTitle(),
+            'twoLetterIsoCode' => $this->getTwoLetterIsoCode(),
+            'hreflang' => $this->getHreflang(),
+            'direction' => $this->getDirection(),
+            'typo3Language' => $this->getTypo3Language(),
+            'flagIdentifier' => $this->getFlagIdentifier(),
+            'fallbackType' => $this->getFallbackType(),
+            'fallbackLanguageIds' => $this->getFallbackLanguageIds(),
+        ];
+    }
+
+    /**
+     * @return Site
+     */
+    public function getSite(): Site
+    {
+        return $this->site;
+    }
+
+    /**
+     * @return int
+     */
+    public function getLanguageId(): int
+    {
+        return $this->languageId;
+    }
+
+    /**
+     * @return string
+     */
+    public function getLocale(): string
+    {
+        return $this->locale;
+    }
+
+    /**
+     * @return string
+     */
+    public function getBase(): string
+    {
+        return $this->base;
+    }
+
+    /**
+     * @return string
+     */
+    public function getTitle(): string
+    {
+        return $this->title;
+    }
+
+    /**
+     * @return string
+     */
+    public function getNavigationTitle(): string
+    {
+        return $this->navigationTitle ?? $this->getTitle();
+    }
+
+    /**
+     * @return string
+     */
+    public function getFlagIdentifier(): string
+    {
+        return $this->flagIdentifier;
+    }
+
+    /**
+     * @return string
+     */
+    public function getTypo3Language(): string
+    {
+        return $this->typo3Language;
+    }
+
+    /**
+     * @return string
+     */
+    public function getFallbackType(): string
+    {
+        return $this->fallbackType;
+    }
+
+    /**
+     * Returns the ISO-639-1 language ISO code
+     *
+     * @return string
+     */
+    public function getTwoLetterIsoCode(): string
+    {
+        return $this->twoLetterIsoCode ?? '';
+    }
+
+    /**
+     * Returns the RFC 1766 / 3066 language tag
+     *
+     * @return string
+     */
+    public function getHreflang(): string
+    {
+        return $this->hreflang ?? '';
+    }
+
+    /**
+     * Returns the language direction
+     *
+     * @return string
+     */
+    public function getDirection(): string
+    {
+        return $this->direction ?? '';
+    }
+
+    /**
+     * @return array
+     */
+    public function getFallbackLanguageIds(): array
+    {
+        return $this->fallbackLanguageIds;
+    }
+}
diff --git a/typo3/sysext/core/Classes/Site/SiteFinder.php b/typo3/sysext/core/Classes/Site/SiteFinder.php
new file mode 100644
index 0000000000000000000000000000000000000000..3c92524cde130b7fbb8831d9aab0b4db8bdd3ec0
--- /dev/null
+++ b/typo3/sysext/core/Classes/Site/SiteFinder.php
@@ -0,0 +1,178 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Core\Site;
+
+/*
+ * 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!
+ */
+
+use TYPO3\CMS\Core\Configuration\SiteConfiguration;
+use TYPO3\CMS\Core\Core\Environment;
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
+use TYPO3\CMS\Core\Exception\SiteNotFoundException;
+use TYPO3\CMS\Core\Site\Entity\Site;
+use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Is used in backend and frontend for all places where to read / identify sites and site languages.
+ */
+class SiteFinder
+{
+    /**
+     * @var Site[]
+     */
+    protected $sites = [];
+
+    /**
+     * Short-hand to quickly fetch a site based on a rootPageId
+     *
+     * @var array
+     */
+    protected $mappingRootPageIdToIdentifier = [];
+
+    /**
+     * Fetches all existing configurations as Site objects
+     */
+    public function __construct()
+    {
+        $reader = GeneralUtility::makeInstance(SiteConfiguration::class, Environment::getConfigPath() . '/sites');
+        $sites = $reader->resolveAllExistingSites();
+        foreach ($sites as $identifier => $site) {
+            $this->sites[$identifier] = $site;
+            $this->mappingRootPageIdToIdentifier[$site->getRootPageId()] = $identifier;
+        }
+    }
+
+    /**
+     * Return a list of all configured sites
+     *
+     * @return Site[]
+     */
+    public function getAllSites(): array
+    {
+        return $this->sites;
+    }
+
+    /**
+     * Get a list of all configured base uris of all sites
+     *
+     * @return array
+     */
+    public function getBaseUris(): array
+    {
+        $baseUrls = [];
+        foreach ($this->sites as $site) {
+            /** @var SiteLanguage $language */
+            foreach ($site->getLanguages() as $language) {
+                $baseUrls[$language->getBase()] = $language;
+                if ($language->getLanguageId() === 0) {
+                    $baseUrls[$site->getBase()] = $language;
+                }
+            }
+        }
+        return $baseUrls;
+    }
+
+    /**
+     * Find a site by given root page id
+     *
+     * @param int $rootPageId
+     * @return Site
+     * @throws SiteNotFoundException
+     */
+    public function getSiteByRootPageId(int $rootPageId): Site
+    {
+        if (isset($this->mappingRootPageIdToIdentifier[$rootPageId])) {
+            return $this->sites[$this->mappingRootPageIdToIdentifier[$rootPageId]];
+        }
+        throw new SiteNotFoundException('No site found for root page id ' . $rootPageId, 1521668882);
+    }
+
+    /**
+     * Get a site language by given base URI
+     *
+     * @param string $uri
+     * @return mixed|null
+     */
+    public function getSiteLanguageByBase(string $uri)
+    {
+        $baseUris = $this->getBaseUris();
+        $bestMatchedUri = null;
+        foreach ($baseUris as $base => $language) {
+            if (strpos($uri, $base) === 0 && strlen($bestMatchedUri ?? '') < strlen($base)) {
+                $bestMatchedUri = $base;
+            }
+        }
+        $siteLanguage = $baseUris[$bestMatchedUri] ?? null;
+        if ($siteLanguage instanceof Site) {
+            $siteLanguage = $siteLanguage->getLanguageById(0);
+        }
+        return $siteLanguage;
+    }
+
+    /**
+     * Find a site by given identifier
+     *
+     * @param string $identifier
+     * @return Site
+     * @throws SiteNotFoundException
+     */
+    public function getSiteByIdentifier(string $identifier): Site
+    {
+        if (isset($this->sites[$identifier])) {
+            return $this->sites[$identifier];
+        }
+        throw new SiteNotFoundException('No site found for identifier ' . $identifier, 1521716628);
+    }
+
+    /**
+     * Traverses the rootline of a page up until a Site was found.
+     *
+     * @param int $pageId
+     * @param array $alternativeRootLine
+     * @return Site
+     * @throws SiteNotFoundException
+     */
+    public function getSiteByPageId(int $pageId, array $alternativeRootLine = null): Site
+    {
+        if (is_array($alternativeRootLine)) {
+            foreach ($alternativeRootLine as $pageInRootLine) {
+                if ($pageInRootLine['uid'] > 0) {
+                    try {
+                        return $this->getSiteByRootPageId((int)$pageInRootLine['uid']);
+                    } catch (SiteNotFoundException $e) {
+                        // continue looping
+                    }
+                }
+            }
+        }
+        // Do your own root line traversing
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
+        $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
+        $queryBuilder->select('pid')->from('pages');
+        $rootLinePageId = $pageId;
+        while ($rootLinePageId > 0) {
+            try {
+                return $this->getSiteByRootPageId($rootLinePageId);
+            } catch (SiteNotFoundException $e) {
+                // get parent page ID
+                $queryBuilder->where(
+                    $queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($rootLinePageId))
+                );
+                $rootLinePageId = (int)$queryBuilder->execute()->fetchColumn(0);
+            }
+        }
+        throw new SiteNotFoundException('No site found in root line of page  ' . $pageId, 1521716622);
+    }
+}
diff --git a/typo3/sysext/core/Configuration/DefaultConfiguration.php b/typo3/sysext/core/Configuration/DefaultConfiguration.php
index b9142a62e8d595a222795abf6ec318b06d8b59d6..f94d9fe03386d098c25ba49de58fda8bc7bfe852 100644
--- a/typo3/sysext/core/Configuration/DefaultConfiguration.php
+++ b/typo3/sysext/core/Configuration/DefaultConfiguration.php
@@ -802,6 +802,226 @@ return [
                         ],
                     ],
                 ],
+                'siteConfiguration' => [
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\InitializeProcessedTca::class => [],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\SiteDatabaseEditRow::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\InitializeProcessedTca::class,
+                        ]
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseParentPageRow::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\SiteDatabaseEditRow::class,
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseUserPermissionCheck::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseDefaultLanguagePageRow::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseParentPageRow::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\InitializeProcessedTca::class,
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseEffectivePid::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseParentPageRow::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseUserPermissionCheck::class
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\DatabasePageRootline::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseEffectivePid::class,
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\UserTsConfig::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\DatabasePageRootline::class,
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\PageTsConfig::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseEffectivePid::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\UserTsConfig::class
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\InlineOverrideChildTca::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\PageTsConfig::class,
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\ParentPageTca::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\InlineOverrideChildTca::class
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRowInitializeNew::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseUserPermissionCheck::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\UserTsConfig::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\PageTsConfig::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\InitializeProcessedTca::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\ParentPageTca::class,
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseUniqueUidNewRow::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRowInitializeNew::class,
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRowDateTimeFields::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseUniqueUidNewRow::class,
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRowDefaultValues::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\InitializeProcessedTca::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRowInitializeNew::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRowDateTimeFields::class
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRecordOverrideValues::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRowDefaultValues::class,
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\TcaGroup::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRecordOverrideValues::class,
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseSystemLanguageRows::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaGroup::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRecordOverrideValues::class,
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRecordTypeValue::class => [
+                        'depends' => [
+                            // As the ctrl.type can hold a nested key we need to resolve all relations
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaGroup::class,
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\PageTsConfigMerged::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\PageTsConfig::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRecordTypeValue::class,
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\TcaColumnsOverrides::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRecordTypeValue::class,
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\TcaInlineExpandCollapseState::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseEditRow::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaColumnsOverrides::class,
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\TcaColumnsProcessCommon::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaInlineExpandCollapseState::class
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\TcaColumnsProcessRecordTitle::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaColumnsProcessCommon::class
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\TcaColumnsProcessPlaceholders::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaColumnsProcessRecordTitle::class
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\TcaColumnsProcessShowitem::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaInlineExpandCollapseState::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaColumnsProcessPlaceholders::class
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\TcaColumnsRemoveUnused::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaColumnsProcessCommon::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaColumnsProcessRecordTitle::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaColumnsProcessPlaceholders::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\InlineOverrideChildTca::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaColumnsProcessShowitem::class,
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\TcaTypesShowitem::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseRecordTypeValue::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\DatabaseSystemLanguageRows::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\InitializeProcessedTca::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaColumnsRemoveUnused::class,
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\TcaColumnsProcessFieldLabels::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaTypesShowitem::class,
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\TcaText::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\InitializeProcessedTca::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaColumnsProcessFieldLabels::class,
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\TcaRadioItems::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\InitializeProcessedTca::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaText::class,
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\TcaCheckboxItems::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\InitializeProcessedTca::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaRadioItems::class
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\SiteTcaSelectItems::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaCheckboxItems::class,
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\TcaSelectItems::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\DatabasePageRootline::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\PageTsConfigMerged::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\InitializeProcessedTca::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaTypesShowitem::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaColumnsRemoveUnused::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaCheckboxItems::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\SiteTcaSelectItems::class,
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\TcaInlineConfiguration::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaSelectItems::class,
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\SiteTcaInline::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaInlineConfiguration::class,
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\TcaInputPlaceholders::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaInlineConfiguration::class,
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\TcaRecordTitle::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\SiteTcaInline::class,
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaInputPlaceholders::class,
+                        ],
+                    ],
+                    \TYPO3\CMS\Backend\Form\FormDataProvider\EvaluateDisplayConditions::class => [
+                        'depends' => [
+                            \TYPO3\CMS\Backend\Form\FormDataProvider\TcaRecordTitle::class,
+                        ],
+                    ],
+                ],
             ],
         ],
     ],
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-84581-SiteHandling.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-84581-SiteHandling.rst
new file mode 100644
index 0000000000000000000000000000000000000000..45c7222329cca3a44444e13c622af05d0169338e
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-84581-SiteHandling.rst
@@ -0,0 +1,157 @@
+.. include:: ../../Includes.txt
+
+=========================================
+Feature: #84581 - Introduce Site Handling
+=========================================
+
+See :issue:`84581`
+
+Description
+===========
+
+Site Handling has been added to TYPO3.
+
+Its goal is to make managing multiple sites easier to understand and faster to do. Sites bring a variety of new
+concepts to TYPO3 which we will explain below.
+
+Take your time and read through the entire document since some concepts rely on each other.
+
+
+typo3conf/sites folder
+----------------------
+
+New sites will live in the folder `typo3conf/sites/`. In the first iteration this folder will contain a file called
+`config.yaml` which holds all configuration for a given site.
+
+In the future this folder can (and should) be used for more files like Fluid templates, and Backend layouts.
+
+
+config.yaml
+-----------
+
+.. code::
+
+    site:
+      # the rootPage Id (see below)
+      rootPageId: 12
+      # my base domain to run this site on. It either accepts a fully qualified URL or "/" to react to any domain name
+      base: 'https://www.example.com/'
+      # The language array
+      languages:
+        -
+          # the TYPO3 sys_language_uid as you know it since... ever
+          languageId: '0'
+          # The internal name for this language. Unused for now, but in the future this will affect display in the backend
+          title: English
+          # optional navigation title which is used in HMENU.special = language
+          navigationTitle: ''
+          # Language base. Accepts either a fully qualified URL or a path segment like "/en/".
+          base: /
+          # sets the locale during frontend rendering
+          locale: en_EN.UTF8
+          # ???
+          iso-639-1: en
+          # FE href language
+          hreflang: en-US
+          # FE text direction
+          direction: ltr
+          # Language Identifier to use in localLang XLIFF files
+          typo3Language: default
+          # Flag Identifier
+          flag: gb
+        -
+          languageId: '1'
+          title: 'danish'
+          navigationTitle: Dansk
+          base: /da/
+          locale: dk_DK.UTF8
+          iso-639-1: da
+          hreflang: dk-DK
+          direction: ltr
+          typo3Language: default
+          flag: dk
+          fallbackType: strict
+        -
+          languageId: '2'
+          title: Deutsch
+          navigationTitle: ''
+          base: 'https://www.beispiel.de'
+          locale: de_DE.UTF-8
+          iso-639-1: de
+          hreflang: de-DE
+          direction: ltr
+          typo3Language: de
+          flag: de
+          # Enable content fallback
+          fallbackType: fallback
+          # Content fallback mode (order is important)
+          fallbacks: '2,1,0'
+      # Error Handling Array (order is important here)
+      # Error Handlers will check the given status code, but the special value "0" will react to any error not configured
+      # elsewhere in this configuration.
+      errorHandling:
+        -
+          # HTTP Status Code to react to
+          errorCode: '404'
+          # The used ErrorHandler. In this case, it's "Display content from Page". See examples below for available options.
+          errorHandler: Page
+          # href to the content source to display (accepts both fully qualified URLs as well as TYPO3 internal link syntax
+          errorContentSource: 't3://page?uid=8'
+        -
+          errorCode: '401'
+          errorHandler: Fluid
+          # Path to the Template File to show
+          errorFluidTemplate: 'EXT:my_extension/Resources/Private/Templates/ErrorPages/401.html'
+          # Optional Templates root path
+          errorFluidTemplatesRootPath: 'EXT:my_extension/Resources/Private/Templates/ErrorPages'
+          # Optional Layouts root path
+          errorFluidLayoutsRootPath: 'EXT:my_extension/Resources/Private/Layouts/ErrorPages'
+          # Optional Partials root path
+          errorFluidPartialsRootPath: 'EXT:my_extension/Resources/Private/Partials/ErrorPages'
+        -
+          errorCode: '0'
+          errorHandler: PHP
+          # Fully qualified class name to a class that implements PageErrorHandlerInterface
+          errorPhpClassFQCN: Vendor\ExtensionName\ErrorHandlers\GenericErrorhandler
+
+
+All settings can also be edited via the backend module `Site Management > Configuration`.
+
+Keep in mind that due to the nature of the module, comments or additional values in your :file:`config.yaml` file
+**will** get deleted on saving.
+
+
+site identifier
+---------------
+
+The site identifier is the name of the folder within `typo3conf/sites/` that will hold your configuration file(s). When
+choosing an identifier make sure to stick to ASCII but you may also use `-`, `_` and `.` for convenience.
+
+
+rootPageId
+----------
+
+Root pages are identified by one of these two properties:
+
+* they are direct descendants of PID 0 (the root root page of TYPO3)
+* they have the "Use as Root Page" property in `pages` set to true.
+
+
+Impact
+======
+
+The following TypoScript settings will be set based on `config.yaml` rather than needing to have them in your TypoScript
+template:
+
+* config.language
+* config.htmlTag_dir
+* config.htmlTag_langKey
+* config.sys_language_uid
+* config.sys_language_mode
+* config.sys_language_isocode
+* config.sys_language_isocode_default
+
+
+Links to pages within a site can now be generated via **any** access of TYPO3, so in both BE and FE as well as CLI mode.
+
+.. index:: Backend, Frontend, TypoScript
\ No newline at end of file
diff --git a/typo3/sysext/frontend/Classes/Controller/ErrorController.php b/typo3/sysext/frontend/Classes/Controller/ErrorController.php
index c6c816a828509c0dfab9b130cf1ab00f4fff3613..0c2b1152a4d05aacfd69b973f9ad3b8d8fd5170a 100644
--- a/typo3/sysext/frontend/Classes/Controller/ErrorController.php
+++ b/typo3/sysext/frontend/Classes/Controller/ErrorController.php
@@ -16,12 +16,15 @@ namespace TYPO3\CMS\Frontend\Controller;
  */
 
 use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Core\Controller\ErrorPageController;
 use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
 use TYPO3\CMS\Core\Error\Http\ServiceUnavailableException;
 use TYPO3\CMS\Core\Http\HtmlResponse;
 use TYPO3\CMS\Core\Http\RedirectResponse;
+use TYPO3\CMS\Core\Site\Entity\Site;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Frontend\PageErrorHandler\PageErrorHandlerInterface;
 
 /**
  * Handles "Page Not Found" or "Page Unavailable" requests,
@@ -33,16 +36,22 @@ class ErrorController
      * Used for creating a 500 response ("Page unavailable"), usually due some misconfiguration
      * but if configured, a RedirectResponse could be returned as well.
      *
+     * @param ServerRequestInterface $request
      * @param string $message
      * @param array $reasons
      * @return ResponseInterface
      * @throws ServiceUnavailableException
      */
-    public function unavailableAction(string $message, array $reasons = []): ResponseInterface
+    public function unavailableAction(ServerRequestInterface $request, string $message, array $reasons = []): ResponseInterface
     {
         if (!$this->isPageUnavailableHandlerConfigured()) {
             throw new ServiceUnavailableException($message, 1518472181);
         }
+        $errorHandler = $this->getErrorHandlerFromSite($request, 500);
+        if ($errorHandler instanceof PageErrorHandlerInterface) {
+            $response = $errorHandler->handlePageError($request, $message, $reasons);
+            return $response->withStatus(500, $message);
+        }
         return $this->handlePageError(
             $GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling'],
             $GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling_statheader'],
@@ -55,13 +64,19 @@ class ErrorController
      * Used for creating a 404 response ("Page Not Found"),
      * but if configured, a RedirectResponse could be returned as well.
      *
+     * @param ServerRequestInterface $request
      * @param string $message
      * @param array $reasons
      * @return ResponseInterface
      * @throws PageNotFoundException
      */
-    public function pageNotFoundAction(string $message, array $reasons = []): ResponseInterface
+    public function pageNotFoundAction(ServerRequestInterface $request, string $message, array $reasons = []): ResponseInterface
     {
+        $errorHandler = $this->getErrorHandlerFromSite($request, 404);
+        if ($errorHandler instanceof PageErrorHandlerInterface) {
+            $response = $errorHandler->handlePageError($request, $message, $reasons);
+            return $response->withStatus(404, $message);
+        }
         if (!$GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling']) {
             throw new PageNotFoundException($message, 1518472189);
         }
@@ -77,12 +92,18 @@ class ErrorController
      * Used for creating a 403 response ("Access denied"),
      * but if configured, a RedirectResponse could be returned as well.
      *
+     * @param ServerRequestInterface $request
      * @param string $message
      * @param array $reasons
      * @return ResponseInterface
      */
-    public function accessDeniedAction(string $message, array $reasons = []): ResponseInterface
+    public function accessDeniedAction(ServerRequestInterface $request, string $message, array $reasons = []): ResponseInterface
     {
+        $errorHandler = $this->getErrorHandlerFromSite($request, 403);
+        if ($errorHandler instanceof PageErrorHandlerInterface) {
+            $response = $errorHandler->handlePageError($request, $message, $reasons);
+            return $response->withStatus(403, $message);
+        }
         return $this->handlePageError(
             $GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling'],
             $GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling_accessdeniedheader'],
@@ -250,7 +271,7 @@ class ErrorController
 
     /**
      * Headers which have been requested, will be added to the response object.
-     * If a header is part of the HTTP Repsonse code, the response object will be annotated as well.
+     * If a header is part of the HTTP Response code, the response object will be annotated as well.
      *
      * @param ResponseInterface $response
      * @param string $headers
@@ -275,4 +296,17 @@ class ErrorController
         }
         return $response;
     }
+
+    /**
+     * Checks if a site is configured, and an error handler is configured for this specific status code.
+     *
+     * @param ServerRequestInterface $request
+     * @param int $statusCode
+     * @return PageErrorHandlerInterface|null
+     */
+    protected function getErrorHandlerFromSite(ServerRequestInterface $request, int $statusCode): ?PageErrorHandlerInterface
+    {
+        $site = $request->getAttribute('site');
+        return $site instanceof Site ? $site->getErrorHandler($statusCode) : $site;
+    }
 }
diff --git a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
index 03644ee55d69f4fc49c78a42177c82ecceb07d96..4bbc47fa40706c7b835c291ef0c82bc86b447101 100644
--- a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
+++ b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
@@ -16,6 +16,7 @@ namespace TYPO3\CMS\Frontend\Controller;
 
 use Doctrine\DBAL\DBALException;
 use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
 use Psr\Log\LoggerAwareInterface;
 use Psr\Log\LoggerAwareTrait;
 use TYPO3\CMS\Backend\FrontendBackendUserAuthentication;
@@ -41,6 +42,7 @@ use TYPO3\CMS\Core\Log\LogManager;
 use TYPO3\CMS\Core\Page\PageRenderer;
 use TYPO3\CMS\Core\Resource\StorageRepository;
 use TYPO3\CMS\Core\Service\DependencyOrderingService;
+use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
@@ -852,7 +854,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
             );
             $this->logger->emergency($message, ['exception' => $exception]);
             try {
-                $response = GeneralUtility::makeInstance(ErrorController::class)->unavailableAction($message);
+                $response = GeneralUtility::makeInstance(ErrorController::class)->unavailableAction($GLOBALS['TYPO3_REQUEST'], $message);
                 $this->sendResponseAndExit($response);
             } catch (ServiceUnavailableException $e) {
                 throw new ServiceUnavailableException($message, 1301648782);
@@ -1275,7 +1277,11 @@ class TypoScriptFrontendController implements LoggerAwareInterface
         // We find the first page belonging to the current domain
         $timeTracker->push('fetch_the_id domain/', '');
         // The page_id of the current domain
-        $this->domainStartPage = $this->findDomainRecord($GLOBALS['TYPO3_CONF_VARS']['SYS']['recursiveDomainSearch']);
+        if ($this->getCurrentSiteLanguage()) {
+            $this->domainStartPage = $this->getCurrentSiteLanguage()->getSite()->getRootPageId();
+        } else {
+            $this->domainStartPage = $this->findDomainRecord($GLOBALS['TYPO3_CONF_VARS']['SYS']['recursiveDomainSearch']);
+        }
         if (!$this->id) {
             if ($this->domainStartPage) {
                 // If the id was not previously set, set it to the id of the domain.
@@ -1289,7 +1295,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                     $message = 'No pages are found on the rootlevel!';
                     $this->logger->alert($message);
                     try {
-                        $response = GeneralUtility::makeInstance(ErrorController::class)->unavailableAction($message);
+                        $response = GeneralUtility::makeInstance(ErrorController::class)->unavailableAction($GLOBALS['TYPO3_REQUEST'], $message);
                         $this->sendResponseAndExit($response);
                     } catch (ServiceUnavailableException $e) {
                         throw new ServiceUnavailableException($message, 1301648975);
@@ -1318,9 +1324,9 @@ class TypoScriptFrontendController implements LoggerAwareInterface
             ];
             $message = $pNotFoundMsg[$this->pageNotFound];
             if ($this->pageNotFound === 1 || $this->pageNotFound === 2) {
-                $response = GeneralUtility::makeInstance(ErrorController::class)->accessDeniedAction($message, $this->getPageAccessFailureReasons());
+                $response = GeneralUtility::makeInstance(ErrorController::class)->accessDeniedAction($GLOBALS['TYPO3_REQUEST'], $message, $this->getPageAccessFailureReasons());
             } else {
-                $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction($message, $this->getPageAccessFailureReasons());
+                $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction($GLOBALS['TYPO3_REQUEST'], $message, $this->getPageAccessFailureReasons());
             }
             $this->sendResponseAndExit($response);
         }
@@ -1411,7 +1417,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                 $message = 'The requested page does not exist!';
                 $this->logger->error($message);
                 try {
-                    $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction($message, $this->getPageAccessFailureReasons());
+                    $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction($GLOBALS['TYPO3_REQUEST'], $message, $this->getPageAccessFailureReasons());
                     $this->sendResponseAndExit($response);
                 } catch (PageNotFoundException $e) {
                     throw new PageNotFoundException($message, 1301648780);
@@ -1423,7 +1429,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
             $message = 'The requested page does not exist!';
             $this->logger->error($message);
             try {
-                $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction($message, $this->getPageAccessFailureReasons());
+                $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction($GLOBALS['TYPO3_REQUEST'], $message, $this->getPageAccessFailureReasons());
                 $this->sendResponseAndExit($response);
             } catch (PageNotFoundException $e) {
                 throw new PageNotFoundException($message, 1301648781);
@@ -1462,7 +1468,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
             $message = 'The requested page didn\'t have a proper connection to the tree-root!';
             $this->logger->error($message);
             try {
-                $response = GeneralUtility::makeInstance(ErrorController::class)->unavailableAction($message, $this->getPageAccessFailureReasons());
+                $response = GeneralUtility::makeInstance(ErrorController::class)->unavailableAction($GLOBALS['TYPO3_REQUEST'], $message, $this->getPageAccessFailureReasons());
                 $this->sendResponseAndExit($response);
             } catch (ServiceUnavailableException $e) {
                 throw new ServiceUnavailableException($message, 1301648167);
@@ -1473,7 +1479,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
             if (empty($this->rootLine)) {
                 $message = 'The requested page was not accessible!';
                 try {
-                    $response = GeneralUtility::makeInstance(ErrorController::class)->unavailableAction($message, $this->getPageAccessFailureReasons());
+                    $response = GeneralUtility::makeInstance(ErrorController::class)->unavailableAction($GLOBALS['TYPO3_REQUEST'], $message, $this->getPageAccessFailureReasons());
                     $this->sendResponseAndExit($response);
                 } catch (ServiceUnavailableException $e) {
                     $this->logger->warning($message);
@@ -2163,7 +2169,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
             $cHash_calc = $this->cacheHash->calculateCacheHash($this->cHash_array);
             if (!hash_equals($cHash_calc, $this->cHash)) {
                 if ($GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFoundOnCHashError']) {
-                    $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction('Request parameters could not be validated (&cHash comparison failed)');
+                    $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction($GLOBALS['TYPO3_REQUEST'], 'Request parameters could not be validated (&cHash comparison failed)');
                     $this->sendResponseAndExit($response);
                 } else {
                     $this->disableCache();
@@ -2191,7 +2197,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                 if ($this->tempContent) {
                     $this->clearPageCacheContent();
                 }
-                $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction('Request parameters could not be validated (&cHash empty)');
+                $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction($GLOBALS['TYPO3_REQUEST'], 'Request parameters could not be validated (&cHash empty)');
                 $this->sendResponseAndExit($response);
             } else {
                 $this->disableCache();
@@ -2409,11 +2415,15 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      */
     protected function createHashBase($createLockHashBase = false)
     {
+        // Ensure the language base is used for the hash base calculation as well, otherwise TypoScript and page-related rendering
+        // is not cached properly as we don't have any language-specific conditions anymore
+        $siteBase = $this->getCurrentSiteLanguage() ? $this->getCurrentSiteLanguage()->getBase() : '';
         $hashParameters = [
             'id' => (int)$this->id,
             'type' => (int)$this->type,
             'gr_list' => (string)$this->gr_list,
             'MP' => (string)$this->MP,
+            'siteBase' => $siteBase,
             'cHash' => $this->cHash_array,
             'domainStartPage' => $this->domainStartPage
         ];
@@ -2457,7 +2467,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                     $message = 'The page is not configured! [type=' . $this->type . '][' . $this->sPre . '].';
                     $this->logger->alert($message);
                     try {
-                        $response = GeneralUtility::makeInstance(ErrorController::class)->unavailableAction($message);
+                        $response = GeneralUtility::makeInstance(ErrorController::class)->unavailableAction($GLOBALS['TYPO3_REQUEST'], $message);
                         $this->sendResponseAndExit($response);
                     } catch (ServiceUnavailableException $e) {
                         $explanation = 'This means that there is no TypoScript object of type PAGE with typeNum=' . $this->type . ' configured.';
@@ -2506,7 +2516,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                 $message = 'No TypoScript template found!';
                 $this->logger->alert($message);
                 try {
-                    $response = GeneralUtility::makeInstance(ErrorController::class)->unavailableAction($message);
+                    $response = GeneralUtility::makeInstance(ErrorController::class)->unavailableAction($GLOBALS['TYPO3_REQUEST'], $message);
                     $this->sendResponseAndExit($response);
                 } catch (ServiceUnavailableException $e) {
                     throw new ServiceUnavailableException($message, 1294587218);
@@ -2525,6 +2535,12 @@ class TypoScriptFrontendController implements LoggerAwareInterface
             ArrayUtility::mergeRecursiveWithOverrule($modifiedGetVars, GeneralUtility::_GET());
             GeneralUtility::_GETset($modifiedGetVars);
         }
+
+        // Auto-configure settings when a site is configured
+        if ($this->getCurrentSiteLanguage()) {
+            $this->config['config']['absRefPrefix'] = $this->config['config']['absRefPrefix'] ?? 'auto';
+        }
+
         // Hook for postProcessing the configuration array
         $params = ['config' => &$this->config['config']];
         foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['configArrayPostProc'] ?? [] as $funcRef) {
@@ -2551,8 +2567,14 @@ class TypoScriptFrontendController implements LoggerAwareInterface
             GeneralUtility::callUserFunction($_funcRef, $_params, $this);
         }
 
+        $siteLanguage = $this->getCurrentSiteLanguage();
+
         // Initialize charset settings etc.
-        $languageKey = $this->config['config']['language'] ?? 'default';
+        if ($siteLanguage) {
+            $languageKey = $siteLanguage->getTypo3Language();
+        } else {
+            $languageKey = $this->config['config']['language'] ?? 'default';
+        }
         $this->lang = $languageKey;
         $this->setOutputLanguage($languageKey);
 
@@ -2561,11 +2583,27 @@ class TypoScriptFrontendController implements LoggerAwareInterface
             $this->metaCharset = $this->config['config']['metaCharset'];
         }
 
-        // Get values from TypoScript, if not set before
-        if ($this->sys_language_uid === 0) {
-            $this->sys_language_uid = ($this->sys_language_content = (int)$this->config['config']['sys_language_uid']);
+        // Get values from site language
+        if ($siteLanguage) {
+            $this->sys_language_uid = ($this->sys_language_content = $siteLanguage->getLanguageId());
+            $this->sys_language_mode = $siteLanguage->getFallbackType();
+            if ($this->sys_language_mode === 'fallback') {
+                $fallbackOrder = $siteLanguage->getFallbackLanguageIds();
+                $fallbackOrder[] = 'pageNotFound';
+            }
+        } else {
+            // Get values from TypoScript, if not set before
+            if ($this->sys_language_uid === 0) {
+                $this->sys_language_uid = ($this->sys_language_content = (int)$this->config['config']['sys_language_uid']);
+            }
+            list($this->sys_language_mode, $fallbackOrder) = GeneralUtility::trimExplode(';', $this->config['config']['sys_language_mode']);
+            if (!empty($fallbackOrder)) {
+                $fallBackOrder = GeneralUtility::trimExplode(',', $fallbackOrder);
+            } else {
+                $fallBackOrder = [0];
+            }
         }
-        list($this->sys_language_mode, $sys_language_content) = GeneralUtility::trimExplode(';', $this->config['config']['sys_language_mode']);
+
         $this->sys_language_contentOL = $this->config['config']['sys_language_overlay'];
         // If sys_language_uid is set to another language than default:
         if ($this->sys_language_uid > 0) {
@@ -2580,20 +2618,20 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                 if ($this->sys_language_uid) {
                     // If requested translation is not available:
                     if (GeneralUtility::hideIfNotTranslated($this->page['l18n_cfg'])) {
-                        $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction('Page is not available in the requested language.');
+                        $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction($GLOBALS['TYPO3_REQUEST'], 'Page is not available in the requested language.');
                         $this->sendResponseAndExit($response);
                     } else {
                         switch ((string)$this->sys_language_mode) {
                             case 'strict':
-                                $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction('Page is not available in the requested language (strict).');
+                                $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction($GLOBALS['TYPO3_REQUEST'], 'Page is not available in the requested language (strict).');
                                 $this->sendResponseAndExit($response);
                                 break;
+                            case 'fallback':
                             case 'content_fallback':
                                 // Setting content uid (but leaving the sys_language_uid) when a content_fallback
                                 // value was found.
-                                $fallBackOrder = GeneralUtility::trimExplode(',', $sys_language_content);
-                                foreach ($fallBackOrder as $orderValue) {
-                                    if ($orderValue === '0' || $orderValue === '') {
+                                foreach ($fallBackOrder ?? [] as $orderValue) {
+                                    if ($orderValue === '0' || $orderValue === 0 || $orderValue === '') {
                                         $this->sys_language_content = 0;
                                         break;
                                     }
@@ -2630,14 +2668,16 @@ class TypoScriptFrontendController implements LoggerAwareInterface
         if ((!$this->sys_language_uid || !$this->sys_language_content) && GeneralUtility::hideIfDefaultLanguage($this->page['l18n_cfg'])) {
             $message = 'Page is not available in default language.';
             $this->logger->error($message);
-            $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction($message);
+            $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction($GLOBALS['TYPO3_REQUEST'], $message);
             $this->sendResponseAndExit($response);
         }
         $this->updateRootLinesWithTranslations();
 
         // Finding the ISO code for the currently selected language
         // fetched by the sys_language record when not fetching content from the default language
-        if ($this->sys_language_content > 0) {
+        if ($siteLanguage = $this->getCurrentSiteLanguage()) {
+            $this->sys_language_isocode = $siteLanguage->getTwoLetterIsoCode();
+        } elseif ($this->sys_language_content > 0) {
             // using sys_language_content because the ISO code only (currently) affect content selection from FlexForms - which should follow "sys_language_content"
             // Set the fourth parameter to TRUE in the next two getRawRecord() calls to
             // avoid versioning overlay to be applied as it generates an SQL error
@@ -2682,8 +2722,13 @@ class TypoScriptFrontendController implements LoggerAwareInterface
     public function settingLocale()
     {
         // Setting locale
-        if ($this->config['config']['locale_all']) {
-            $availableLocales = GeneralUtility::trimExplode(',', $this->config['config']['locale_all'], true);
+        $locale = $this->config['config']['locale_all'];
+        $siteLanguage = $this->getCurrentSiteLanguage();
+        if ($siteLanguage) {
+            $locale = $siteLanguage->getLocale();
+        }
+        if ($locale) {
+            $availableLocales = GeneralUtility::trimExplode(',', $locale, true);
             // If LC_NUMERIC is set e.g. to 'de_DE' PHP parses float values locale-aware resulting in strings with comma
             // as decimal point which causes problems with value conversions - so we set all locale types except LC_NUMERIC
             // @see https://bugs.php.net/bug.php?id=53711
@@ -2692,13 +2737,13 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                 // As str_* methods are locale aware and turkish has no upper case I
                 // Class autoloading and other checks depending on case changing break with turkish locale LC_CTYPE
                 // @see http://bugs.php.net/bug.php?id=35050
-                if (substr($this->config['config']['locale_all'], 0, 2) !== 'tr') {
+                if (substr($locale, 0, 2) !== 'tr') {
                     setlocale(LC_CTYPE, ...$availableLocales);
                 }
                 setlocale(LC_MONETARY, ...$availableLocales);
                 setlocale(LC_TIME, ...$availableLocales);
             } else {
-                $this->getTimeTracker()->setTSlogMessage('Locale "' . htmlspecialchars($this->config['config']['locale_all']) . '" not found.', 3);
+                $this->getTimeTracker()->setTSlogMessage('Locale "' . htmlspecialchars($locale) . '" not found.', 3);
             }
         }
     }
@@ -4725,4 +4770,18 @@ class TypoScriptFrontendController implements LoggerAwareInterface
     {
         return GeneralUtility::makeInstance(TimeTracker::class);
     }
+
+    /**
+     * Returns the currently configured "site language" if a site is configured (= resolved) in the current request.
+     *
+     * @internal
+     */
+    protected function getCurrentSiteLanguage(): ?SiteLanguage
+    {
+        if ($GLOBALS['TYPO3_REQUEST'] instanceof ServerRequestInterface
+            && $GLOBALS['TYPO3_REQUEST']->getAttribute('language') instanceof SiteLanguage) {
+            return $GLOBALS['TYPO3_REQUEST']->getAttribute('language');
+        }
+        return null;
+    }
 }
diff --git a/typo3/sysext/frontend/Classes/Middleware/MaintenanceMode.php b/typo3/sysext/frontend/Classes/Middleware/MaintenanceMode.php
index 3709350dbd288120ff70010b4ff524451a614a86..6105a43c60ba5442f47f9950f510968b5437fe85 100644
--- a/typo3/sysext/frontend/Classes/Middleware/MaintenanceMode.php
+++ b/typo3/sysext/frontend/Classes/Middleware/MaintenanceMode.php
@@ -47,7 +47,7 @@ class MaintenanceMode implements MiddlewareInterface
                 $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask']
             )
         ) {
-            return GeneralUtility::makeInstance(ErrorController::class)->unavailableAction('This page is temporarily unavailable.');
+            return GeneralUtility::makeInstance(ErrorController::class)->unavailableAction($GLOBALS['TYPO3_REQUEST'], 'This page is temporarily unavailable.');
         }
         // Continue the regular stack if no maintenance mode is active
         return $handler->handle($request);
diff --git a/typo3/sysext/frontend/Classes/Middleware/SiteResolver.php b/typo3/sysext/frontend/Classes/Middleware/SiteResolver.php
new file mode 100644
index 0000000000000000000000000000000000000000..fc8368e19d0a863fe820a3aa406259dbcb87c2e4
--- /dev/null
+++ b/typo3/sysext/frontend/Classes/Middleware/SiteResolver.php
@@ -0,0 +1,88 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Frontend\Middleware;
+
+/*
+ * 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!
+ */
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\MiddlewareInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use TYPO3\CMS\Core\Exception\SiteNotFoundException;
+use TYPO3\CMS\Core\Site\Entity\Site;
+use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
+use TYPO3\CMS\Core\Site\SiteFinder;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Identify the current request and resolve the site to it.
+ * After that middleware, TSFE should be populated with
+ * - language configuration
+ * - site configuration
+ *
+ * Properties like config.sys_language_uid and config.language is then set for TypoScript.
+ */
+class SiteResolver implements MiddlewareInterface
+{
+    /**
+     * Resolve the site/language information by checking the page ID or the URL.
+     *
+     * @param ServerRequestInterface $request
+     * @param RequestHandlerInterface $handler
+     * @return ResponseInterface
+     */
+    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+    {
+        $finder = GeneralUtility::makeInstance(SiteFinder::class);
+
+        $site = null;
+        $language = null;
+
+        $pageId = $request->getQueryParams()['id'] ?? $request->getParsedBody()['id'] ?? 0;
+        $languageId = $request->getQueryParams()['L'] ?? $request->getParsedBody()['L'] ?? null;
+
+        // 1. Check if we have a _GET/_POST parameter for "id", then a site information can be resolved based.
+        if ($pageId > 0 && $languageId !== null) {
+            // Loop over the whole rootline without permissions to get the actual site information
+            try {
+                $site = $finder->getSiteByPageId((int)$pageId);
+                $language = $site->getLanguageById($languageId);
+            } catch (SiteNotFoundException $e) {
+            }
+        }
+        if (!($language instanceof SiteLanguage)) {
+            // 2. Check if there is a site language, if not, just don't do anything
+            $language = $finder->getSiteLanguageByBase((string)$request->getUri());
+            // @todo: use exception for getSiteLanguageByBase
+            if ($language) {
+                $site = $language->getSite();
+            }
+        }
+
+        // Add language+site information to the PSR-7 request object.
+        if ($language instanceof SiteLanguage && $site instanceof Site) {
+            $request = $request->withAttribute('site', $site);
+            $request = $request->withAttribute('language', $language);
+            $queryParams = $request->getQueryParams();
+            // necessary to calculate the proper hash base
+            $queryParams['L'] = $language->getLanguageId();
+            $request = $request->withQueryParams($queryParams);
+            $_GET['L'] = $queryParams['L'];
+            // At this point, we later get further route modifiers
+            // for bw-compat we update $GLOBALS[TYPO3_REQUEST] to be used later in TSFE.
+            $GLOBALS['TYPO3_REQUEST'] = $request;
+        }
+        return $handler->handle($request);
+    }
+}
diff --git a/typo3/sysext/frontend/Classes/Page/PageGenerator.php b/typo3/sysext/frontend/Classes/Page/PageGenerator.php
index bae36916de61a90044b653762c6ceb35ff75ff9a..835fceb88bcc1ecd4d8dd402f51a739b935493fe 100644
--- a/typo3/sysext/frontend/Classes/Page/PageGenerator.php
+++ b/typo3/sysext/frontend/Classes/Page/PageGenerator.php
@@ -14,7 +14,9 @@ namespace TYPO3\CMS\Frontend\Page;
  * The TYPO3 project - inspiring people to share!
  */
 
+use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Core\Page\PageRenderer;
+use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
 use TYPO3\CMS\Core\Type\File\ImageInfo;
 use TYPO3\CMS\Core\TypoScript\TypoScriptService;
@@ -103,9 +105,14 @@ class PageGenerator
         $tsfe->content = '';
         $htmlTagAttributes = [];
         $htmlLang = $tsfe->config['config']['htmlTag_langKey'] ?: ($tsfe->sys_language_isocode ?: 'en');
-        // Set content direction: (More info: http://www.tau.ac.il/~danon/Hebrew/HTML_and_Hebrew.html)
-        if ($tsfe->config['config']['htmlTag_dir']) {
-            $htmlTagAttributes['dir'] = htmlspecialchars($tsfe->config['config']['htmlTag_dir']);
+        // Set content direction
+        // More info: http://www.tau.ac.il/~danon/Hebrew/HTML_and_Hebrew.html)
+        $direction = $tsfe->config['config']['htmlTag_dir'];
+        if (self::getCurrentSiteLanguage()) {
+            $direction = self::getCurrentSiteLanguage()->getDirection();
+        }
+        if ($direction) {
+            $htmlTagAttributes['dir'] = htmlspecialchars($direction);
         }
         // Setting document type:
         $docTypeParts = [];
@@ -930,4 +937,18 @@ class PageGenerator
             );
         }
     }
+
+    /**
+     * Returns the currently configured "site language" if a site is configured (= resolved) in the current request.
+     *
+     * @internal
+     */
+    protected static function getCurrentSiteLanguage(): ?SiteLanguage
+    {
+        if ($GLOBALS['TYPO3_REQUEST'] instanceof ServerRequestInterface
+            && $GLOBALS['TYPO3_REQUEST']->getAttribute('language') instanceof SiteLanguage) {
+            return $GLOBALS['TYPO3_REQUEST']->getAttribute('language');
+        }
+        return null;
+    }
 }
diff --git a/typo3/sysext/frontend/Classes/PageErrorHandler/DefaultPHPErrorHandler.php b/typo3/sysext/frontend/Classes/PageErrorHandler/DefaultPHPErrorHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..9c6b37aa751a0701768925791af276cddbafea26
--- /dev/null
+++ b/typo3/sysext/frontend/Classes/PageErrorHandler/DefaultPHPErrorHandler.php
@@ -0,0 +1,81 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Frontend\PageErrorHandler;
+
+/*
+ * 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!
+ */
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Core\Http\HtmlResponse;
+
+class DefaultPHPErrorHandler implements PageErrorHandlerInterface
+{
+
+    /*
+ /$$$$$$$
+| $$__  $$
+| $$  \ $$  /$$$$$$  /$$$$$$/$$$$   /$$$$$$  /$$    /$$ /$$$$$$
+| $$$$$$$/ /$$__  $$| $$_  $$_  $$ /$$__  $$|  $$  /$$//$$__  $$
+| $$__  $$| $$$$$$$$| $$ \ $$ \ $$| $$  \ $$ \  $$/$$/| $$$$$$$$
+| $$  \ $$| $$_____/| $$ | $$ | $$| $$  | $$  \  $$$/ | $$_____/
+| $$  | $$|  $$$$$$$| $$ | $$ | $$|  $$$$$$/   \  $/  |  $$$$$$$
+|__/  |__/ \_______/|__/ |__/ |__/ \______/     \_/    \_______/
+
+
+
+               /$$
+              | $$
+ /$$  /$$  /$$| $$$$$$$   /$$$$$$  /$$$$$$$
+| $$ | $$ | $$| $$__  $$ /$$__  $$| $$__  $$
+| $$ | $$ | $$| $$  \ $$| $$$$$$$$| $$  \ $$
+| $$ | $$ | $$| $$  | $$| $$_____/| $$  | $$
+|  $$$$$/$$$$/| $$  | $$|  $$$$$$$| $$  | $$
+ \_____/\___/ |__/  |__/ \_______/|__/  |__/
+
+
+
+                                                             /$$
+                                                            | $$
+ /$$$$$$/$$$$   /$$$$$$   /$$$$$$   /$$$$$$   /$$$$$$   /$$$$$$$
+| $$_  $$_  $$ /$$__  $$ /$$__  $$ /$$__  $$ /$$__  $$ /$$__  $$
+| $$ \ $$ \ $$| $$$$$$$$| $$  \__/| $$  \ $$| $$$$$$$$| $$  | $$
+| $$ | $$ | $$| $$_____/| $$      | $$  | $$| $$_____/| $$  | $$
+| $$ | $$ | $$|  $$$$$$$| $$      |  $$$$$$$|  $$$$$$$|  $$$$$$$
+|__/ |__/ |__/ \_______/|__/       \____  $$ \_______/ \_______/
+                                   /$$  \ $$
+                                  |  $$$$$$/
+                                   \______/
+     */
+
+    /**
+     * @var int
+     */
+    protected $statusCode;
+
+    public function __construct(int $statusCode, array $configuration)
+    {
+        $this->statusCode = $statusCode;
+    }
+
+    /**
+     * @param ServerRequestInterface $request
+     * @param string $message
+     * @param array $reasons
+     * @return ResponseInterface
+     */
+    public function handlePageError(ServerRequestInterface $request, string $message, array $reasons = []): ResponseInterface
+    {
+        return new HtmlResponse('go away', $this->statusCode);
+    }
+}
diff --git a/typo3/sysext/frontend/Classes/PageErrorHandler/FluidPageErrorHandler.php b/typo3/sysext/frontend/Classes/PageErrorHandler/FluidPageErrorHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..1734ac7eefa412e43f3df19ac5e4a2efbe9e2593
--- /dev/null
+++ b/typo3/sysext/frontend/Classes/PageErrorHandler/FluidPageErrorHandler.php
@@ -0,0 +1,77 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Frontend\PageErrorHandler;
+
+/*
+ * 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!
+ */
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Core\Http\HtmlResponse;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Fluid\View\TemplateView;
+use TYPO3Fluid\Fluid\View\ViewInterface;
+
+/**
+ * An error handler that renders a fluid template.
+ * This is typically configured via the "Sites configuration" module in the backend.
+ */
+class FluidPageErrorHandler implements PageErrorHandlerInterface
+{
+    /**
+     * @var ViewInterface
+     */
+    protected $view;
+
+    /**
+     * @var int
+     */
+    protected $statusCode;
+
+    /**
+     * FluidPageErrorHandler constructor.
+     * @param int $statusCode
+     * @param array $configuration
+     */
+    public function __construct(int $statusCode, array $configuration)
+    {
+        $this->statusCode = $statusCode;
+        $this->view = GeneralUtility::makeInstance(TemplateView::class);
+        if (!empty($configuration['errorFluidTemplatesRootPath'])) {
+            $this->view->setTemplateRootPaths([$configuration['errorFluidTemplatesRootPath']]);
+        }
+        if (!empty($configuration['errorFluidLayoutsRootPath'])) {
+            $this->view->setLayoutRootPaths([$configuration['errorFluidLayoutsRootPath']]);
+        }
+        if (!empty($configuration['errorFluidPartialsRootPath'])) {
+            $this->view->setPartialRootPaths([$configuration['errorFluidPartialsRootPath']]);
+        }
+        $this->view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($configuration['errorFluidTemplate']));
+    }
+
+    /**
+     * @param ServerRequestInterface $request
+     * @param string $message
+     * @param array $reasons
+     * @return ResponseInterface
+     */
+    public function handlePageError(ServerRequestInterface $request, string $message, array $reasons = []): ResponseInterface
+    {
+        $this->view->assignMultiple([
+            'request' => $request,
+            'message' => $message,
+            'reasons' => $reasons
+        ]);
+        return new HtmlResponse($this->view->render());
+    }
+}
diff --git a/typo3/sysext/frontend/Classes/PageErrorHandler/PageContentErrorHandler.php b/typo3/sysext/frontend/Classes/PageErrorHandler/PageContentErrorHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..7e06cc89c4c8f9b35fb953c3d112747e894657a1
--- /dev/null
+++ b/typo3/sysext/frontend/Classes/PageErrorHandler/PageContentErrorHandler.php
@@ -0,0 +1,105 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Frontend\PageErrorHandler;
+
+/*
+ * 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!
+ */
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Backend\Routing\PageUriBuilder;
+use TYPO3\CMS\Core\Http\HtmlResponse;
+use TYPO3\CMS\Core\LinkHandling\LinkService;
+use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Install\FolderStructure\Exception\InvalidArgumentException;
+
+/**
+ * Renders the content of a page to be displayed (also in relation to language etc)
+ * This is typically configured via the "Sites configuration" module in the backend.
+ */
+class PageContentErrorHandler implements PageErrorHandlerInterface
+{
+
+    /**
+     * @var int
+     */
+    protected $statusCode;
+
+    /**
+     * @var array
+     */
+    protected $errorHandlerConfiguration;
+
+    /**
+     * PageContentErrorHandler constructor.
+     * @param int $statusCode
+     * @param array $configuration
+     * @throws InvalidArgumentException
+     */
+    public function __construct(int $statusCode, array $configuration)
+    {
+        $this->statusCode = $statusCode;
+        if (empty($configuration['errorContentSource'])) {
+            throw new InvalidArgumentException('PageContentErrorHandler needs to have a proper link set.', 1522826413);
+        }
+        $this->errorHandlerConfiguration = $configuration;
+    }
+
+    /**
+     * @param ServerRequestInterface $request
+     * @param string $message
+     * @param array $reasons
+     * @return ResponseInterface
+     */
+    public function handlePageError(ServerRequestInterface $request, string $message, array $reasons = []): ResponseInterface
+    {
+        $resolvedUrl = $this->resolveUrl($request, $this->errorHandlerConfiguration['errorContentSource']);
+        $content = GeneralUtility::getUrl($resolvedUrl);
+        return new HtmlResponse($content, $this->statusCode);
+    }
+
+    /**
+     * Resolve the URL (currently only page and external URL are supported)
+     *
+     * @param ServerRequestInterface $request
+     * @param string $typoLinkUrl
+     * @return string
+     */
+    protected function resolveUrl(ServerRequestInterface $request, string $typoLinkUrl): string
+    {
+        $linkService = GeneralUtility::makeInstance(LinkService::class);
+        $urlParams = $linkService->resolve($typoLinkUrl);
+        if ($urlParams['type'] !== 'page' && $urlParams['type'] !== 'url') {
+            throw new \InvalidArgumentException('PageContentErrorHandler can only handle TYPO3 urls of types "page" or "url"', 1522826609);
+        }
+        if ($urlParams['type'] === 'url') {
+            return $urlParams['url'];
+        }
+
+        // Build Url
+        $languageUid = null;
+        $siteLanguage = $request->getAttribute('language');
+        if ($siteLanguage instanceof SiteLanguage) {
+            $languageUid = $siteLanguage->getLanguageId();
+        }
+        $uriBuilder = GeneralUtility::makeInstance(PageUriBuilder::class);
+        return (string)$uriBuilder->buildUri(
+            (int)$urlParams['pageuid'],
+            [],
+            null,
+            ['language' => $languageUid],
+            PageUriBuilder::ABSOLUTE_URL
+        );
+    }
+}
diff --git a/typo3/sysext/frontend/Classes/PageErrorHandler/PageErrorHandlerInterface.php b/typo3/sysext/frontend/Classes/PageErrorHandler/PageErrorHandlerInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..82d52a2841a71956735766dd2581a914aaf6de49
--- /dev/null
+++ b/typo3/sysext/frontend/Classes/PageErrorHandler/PageErrorHandlerInterface.php
@@ -0,0 +1,28 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Frontend\PageErrorHandler;
+
+/*
+ * 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!
+ */
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+
+/**
+ * Page error handler interface
+ * Should be implemented by all custom PHP-related Page Error Handlers.
+ */
+interface PageErrorHandlerInterface
+{
+    public function handlePageError(ServerRequestInterface $request, string $message, array $reasons = []): ResponseInterface;
+}
diff --git a/typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php b/typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php
index cc7376dec4f7282096ab1a25ca97317303a6debc..363c31eefb436157abf99974fa8b78da9e919596 100644
--- a/typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php
+++ b/typo3/sysext/frontend/Classes/Typolink/PageLinkBuilder.php
@@ -15,8 +15,11 @@ namespace TYPO3\CMS\Frontend\Typolink;
  * The TYPO3 project - inspiring people to share!
  */
 
+use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Backend\Routing\PageUriBuilder;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
+use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Frontend\ContentObject\TypolinkModifyLinkConfigForPageLinksHookInterface;
@@ -480,6 +483,7 @@ class PageLinkBuilder extends AbstractTypolinkBuilder
      */
     protected function createTotalUrlAndLinkData($page, $target, $no_cache, $addParams = '', $typeOverride = '', $targetDomain = '')
     {
+        $allQueryParameters = [];
         $LD = [];
         // Adding Mount Points, "&MP=", parameter for the current page if any is set
         // but non other set explicitly
@@ -507,25 +511,67 @@ class PageLinkBuilder extends AbstractTypolinkBuilder
         // Override...
         if ($typeNum) {
             $LD['type'] = '&type=' . (int)$typeNum;
+            $allQueryParameters['type'] = (int)$typeNum;
         } else {
             $LD['type'] = '';
         }
         // Preserving the type number.
         $LD['orig_type'] = $LD['type'];
         // noCache
-        $LD['no_cache'] = $no_cache ? '&no_cache=1' : '';
-        // linkVars
-        if ($addParams) {
-            $LD['linkVars'] = GeneralUtility::implodeArrayForUrl('', GeneralUtility::explodeUrl2Array($this->getTypoScriptFrontendController()->linkVars . $addParams), '', false, true);
+        if ($no_cache) {
+            $LD['no_cache'] = '&no_cache=1';
+            $allQueryParameters['no_cache'] = 1;
         } else {
-            $LD['linkVars'] = $this->getTypoScriptFrontendController()->linkVars;
+            $LD['no_cache'] = '';
+        }
+        // linkVars
+        $queryParameters = GeneralUtility::explodeUrl2Array($this->getTypoScriptFrontendController()->linkVars . $addParams);
+        if (!empty($queryParameters)) {
+            $allQueryParameters = array_replace_recursive($queryParameters, $allQueryParameters);
+            $LD['linkVars'] = GeneralUtility::implodeArrayForUrl('', $queryParameters, '', false, true);
         }
         // Add absRefPrefix if exists.
         $LD['url'] = $this->getTypoScriptFrontendController()->absRefPrefix . $LD['url'];
         // If the special key 'sectionIndex_uid' (added 'manually' in tslib/menu.php to the page-record) is set, then the link jumps directly to a section on the page.
         $LD['sectionIndex'] = $page['sectionIndex_uid'] ? '#c' . $page['sectionIndex_uid'] : '';
-        // Compile the normal total url
-        $LD['totalURL'] = rtrim($LD['url'] . $LD['type'] . $LD['no_cache'] . $LD['linkVars'] . $this->getTypoScriptFrontendController()->getMethodUrlIdToken, '?') . $LD['sectionIndex'];
+
+        // Compile the total url
+        $urlParts = parse_url($LD['url']);
+
+        // Now see if the URL can be replaced by a URL generated by the Site-based Page Builder,
+        // but first find out if a language has been set explicitly
+        if ($GLOBALS['TYPO3_REQUEST'] instanceof ServerRequestInterface) {
+            $currentSiteLanguage = $GLOBALS['TYPO3_REQUEST']->getAttribute('language');
+            if ($currentSiteLanguage instanceof SiteLanguage) {
+                $languageId = $currentSiteLanguage->getLanguageId();
+            }
+        }
+        $absRefPrefix = $this->getTypoScriptFrontendController()->absRefPrefix;
+        $languageId = $queryParameters['L'] ?? $languageId ?? null;
+        $totalUrl = (string)GeneralUtility::makeInstance(PageUriBuilder::class)->buildUri(
+            (int)$page['uid'],
+            $allQueryParameters,
+            $LD['sectionIndex'],
+            ['language' => $languageId, 'alternativePageId' => $page['alias'] ?: $page['uid'], 'legacyUrlPrefix' => $absRefPrefix],
+            (!$urlParts['scheme'] && !$urlParts['host']) ? PageUriBuilder::ABSOLUTE_PATH : PageUriBuilder::ABSOLUTE_URL
+        );
+
+        // $totalUri contains /index.php for legacy URLs, as previously "it was index.php"
+        // In case an URI has is prefixed with "/" which is not the absRefPrefix, remove it.
+        // this might change in the future
+        if (strpos($totalUrl, '/index.php') === 0 && strpos($totalUrl, $absRefPrefix) !== 0) {
+            $totalUrl = substr($totalUrl, 1);
+        }
+
+        // Add the method url id token later-on
+        if ($this->getTypoScriptFrontendController()->getMethodUrlIdToken) {
+            if (strpos($totalUrl, '#') !== false) {
+                $totalUrl = str_replace('#', $this->getTypoScriptFrontendController()->getMethodUrlIdToken . '#', $totalUrl);
+            } else {
+                $totalUrl .= $this->getTypoScriptFrontendController()->getMethodUrlIdToken;
+            }
+        }
+        $LD['totalURL'] = $totalUrl;
         // Call post processing function for link rendering:
         $_params = [
             'LD' => &$LD,
diff --git a/typo3/sysext/frontend/Configuration/RequestMiddlewares.php b/typo3/sysext/frontend/Configuration/RequestMiddlewares.php
index f5367b67e5c33cbdec5219a6f718acdf1a566d7c..cc67614ff023814d78c2dba86e8ddb3760f0f076 100644
--- a/typo3/sysext/frontend/Configuration/RequestMiddlewares.php
+++ b/typo3/sysext/frontend/Configuration/RequestMiddlewares.php
@@ -69,12 +69,25 @@ return [
                 'typo3/cms-frontend/tsfe',
             ]
         ],
+        'typo3/cms-frontend/site' => [
+            'target' => \TYPO3\CMS\Frontend\Middleware\SiteResolver::class,
+            'after' => [
+                'typo3/cms-core/normalized-params-attribute',
+                'typo3/cms-frontend/tsfe',
+                'typo3/cms-frontend/authentication',
+                'typo3/cms-frontend/backend-user-authentication',
+            ],
+            'before' => [
+                'typo3/cms-frontend/page-resolver'
+            ]
+        ],
         'typo3/cms-frontend/page-resolver' => [
             'target' => \TYPO3\CMS\Frontend\Middleware\PageResolver::class,
             'after' => [
                 'typo3/cms-frontend/tsfe',
                 'typo3/cms-frontend/authentication',
                 'typo3/cms-frontend/backend-user-authentication',
+                'typo3/cms-frontend/site',
             ]
         ],
     ]
diff --git a/typo3/sysext/frontend/Tests/Unit/Controller/ErrorControllerTest.php b/typo3/sysext/frontend/Tests/Unit/Controller/ErrorControllerTest.php
index 793fb35a75931f5552d2427fe8976b136bf292bd..b1814887ac68edf50f45bc6b53c9aba8cff024eb 100644
--- a/typo3/sysext/frontend/Tests/Unit/Controller/ErrorControllerTest.php
+++ b/typo3/sysext/frontend/Tests/Unit/Controller/ErrorControllerTest.php
@@ -22,6 +22,7 @@ use TYPO3\CMS\Core\Http\HtmlResponse;
 use TYPO3\CMS\Core\Http\RedirectResponse;
 use TYPO3\CMS\Core\Http\RequestFactory;
 use TYPO3\CMS\Core\Http\Response;
+use TYPO3\CMS\Core\Http\ServerRequest;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Frontend\Controller\ErrorController;
 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
@@ -63,8 +64,9 @@ class ErrorControllerTest extends UnitTestCase
         $this->expectExceptionMessage('This test page was not found!');
         $this->expectExceptionCode(1518472189);
         $GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling'] = false;
+        $GLOBALS['TYPO3_REQUEST'] = [];
         $subject = new ErrorController();
-        $subject->pageNotFoundAction('This test page was not found!');
+        $subject->pageNotFoundAction(new ServerRequest(), 'This test page was not found!');
     }
 
     /**
@@ -177,11 +179,12 @@ X-TYPO3-Additional-Header: Banana Stand',
         $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
         $_SERVER['HTTP_HOST'] = 'localhost';
         $_SERVER['SSL_SESSION_ID'] = true;
+        $GLOBALS['TYPO3_REQUEST'] = [];
 
         $this->prophesizeErrorPageController();
 
         $subject = new ErrorController();
-        $response = $subject->pageNotFoundAction($message);
+        $response = $subject->pageNotFoundAction(new ServerRequest(), $message);
         if (is_array($expectedResponseDetails)) {
             $this->assertInstanceOf($expectedResponseDetails['type'], $response);
             $this->assertEquals($expectedResponseDetails['statusCode'], $response->getStatusCode());
@@ -206,11 +209,12 @@ X-TYPO3-Additional-Header: Banana Stand';
         $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
         $_SERVER['HTTP_HOST'] = 'localhost';
         $_SERVER['SSL_SESSION_ID'] = true;
+        $GLOBALS['TYPO3_REQUEST'] = [];
         $this->prophesizeErrorPageController();
         $subject = new ErrorController();
 
         $this->prophesizeGetUrl();
-        $response = $subject->pageNotFoundAction('Custom message');
+        $response = $subject->pageNotFoundAction(new ServerRequest(), 'Custom message');
 
         $expectedResponseDetails = [
             'type' => HtmlResponse::class,
@@ -268,8 +272,9 @@ X-TYPO3-Additional-Header: Banana Stand';
         $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
         $_SERVER['HTTP_HOST'] = 'localhost';
         $_SERVER['SSL_SESSION_ID'] = true;
+        $GLOBALS['TYPO3_REQUEST'] = [];
         $subject = new ErrorController();
-        $response = $subject->accessDeniedAction($message);
+        $response = $subject->accessDeniedAction(new ServerRequest(), $message);
         if (is_array($expectedResponseDetails)) {
             $this->assertInstanceOf($expectedResponseDetails['type'], $response);
             $this->assertEquals($expectedResponseDetails['statusCode'], $response->getStatusCode());
@@ -293,7 +298,7 @@ X-TYPO3-Additional-Header: Banana Stand';
         $this->expectExceptionCode(1518472181);
         $GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling'] = false;
         $subject = new ErrorController();
-        $subject->unavailableAction('All your system are belong to us!');
+        $subject->unavailableAction(new ServerRequest(), 'All your system are belong to us!');
     }
 
     /**
@@ -308,7 +313,7 @@ X-TYPO3-Additional-Header: Banana Stand';
         $this->expectExceptionMessage('All your system are belong to us!');
         $this->expectExceptionCode(1518472181);
         $subject = new ErrorController();
-        $subject->unavailableAction('All your system are belong to us!');
+        $subject->unavailableAction(new ServerRequest(), 'All your system are belong to us!');
     }
 
     /**
@@ -422,10 +427,11 @@ X-TYPO3-Additional-Header: Banana Stand',
         $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
         $_SERVER['HTTP_HOST'] = 'localhost';
         $_SERVER['SSL_SESSION_ID'] = true;
+        $GLOBALS['TYPO3_REQUEST'] = [];
         $this->prophesizeGetUrl();
         $this->prophesizeErrorPageController();
         $subject = new ErrorController();
-        $response = $subject->unavailableAction($message);
+        $response = $subject->unavailableAction(new ServerRequest(), $message);
         if (is_array($expectedResponseDetails)) {
             $this->assertInstanceOf($expectedResponseDetails['type'], $response);
             $this->assertEquals($expectedResponseDetails['statusCode'], $response->getStatusCode());
@@ -451,10 +457,11 @@ X-TYPO3-Additional-Header: Banana Stand';
         $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
         $_SERVER['HTTP_HOST'] = 'localhost';
         $_SERVER['SSL_SESSION_ID'] = true;
+        $GLOBALS['TYPO3_REQUEST'] = [];
         $this->prophesizeErrorPageController();
         $this->prophesizeGetUrl();
         $subject = new ErrorController();
-        $response = $subject->unavailableAction('custom message');
+        $response = $subject->unavailableAction(new ServerRequest(), 'custom message');
 
         $expectedResponseDetails = [
             'type' => HtmlResponse::class,