diff --git a/typo3/sysext/backend/Classes/Controller/Page/TreeController.php b/typo3/sysext/backend/Classes/Controller/Page/TreeController.php
index 464181a9d67a920e3ad2045090df28ab8c1482ad..b035ecaefd80e800406a3c3b68826a1056b94cd9 100644
--- a/typo3/sysext/backend/Classes/Controller/Page/TreeController.php
+++ b/typo3/sysext/backend/Classes/Controller/Page/TreeController.php
@@ -102,13 +102,47 @@ class TreeController
      */
     protected $iconFactory;
 
+    /**
+     * Number of tree levels which should be returned on the first page tree load
+     *
+     * @var int
+     */
+    protected $levelsToFetch = 2;
+
+    /**
+     * When set to true all nodes returend by API will be expanded
+     * @var bool
+     */
+    protected $expandAllNodes = false;
+
     /**
      * Constructor to set up common objects needed in various places.
      */
     public function __construct()
     {
         $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
-        $this->useNavTitle = (bool)($this->getBackendUser()->getTSConfig()['options.']['pageTree.']['showNavTitle'] ?? false);
+    }
+
+    protected function initializeConfiguration()
+    {
+        $userTsConfig = $this->getBackendUser()->getTSConfig();
+        $this->hiddenRecords = GeneralUtility::intExplode(
+            ',',
+            $userTsConfig['options.']['hideRecords.']['pages'] ?? '',
+            true
+        );
+        $this->backgroundColors = $userTsConfig['options.']['pageTree.']['backgroundColor.'] ?? [];
+        $this->addIdAsPrefix = (bool)($userTsConfig['options.']['pageTree.']['showPageIdWithTitle'] ?? false);
+        $this->addDomainName = (bool)($userTsConfig['options.']['pageTree.']['showDomainNameWithTitle'] ?? false);
+        $this->useNavTitle = (bool)($userTsConfig['options.']['pageTree.']['showNavTitle'] ?? false);
+        $this->showMountPathAboveMounts = (bool)($userTsConfig['options.']['pageTree.']['showPathAboveMounts'] ?? false);
+        $backendUserConfiguration = GeneralUtility::makeInstance(BackendUserConfiguration::class);
+        $this->expandedState = $backendUserConfiguration->get('BackendComponents.States.Pagetree');
+        if (is_object($this->expandedState) && is_object($this->expandedState->stateHash)) {
+            $this->expandedState = (array)$this->expandedState->stateHash;
+        } else {
+            $this->expandedState = $this->expandedState['stateHash'] ?: [];
+        }
     }
 
     /**
@@ -178,29 +212,51 @@ class TreeController
      */
     public function fetchDataAction(ServerRequestInterface $request): ResponseInterface
     {
-        $userTsConfig = $this->getBackendUser()->getTSConfig();
-        $this->hiddenRecords = GeneralUtility::intExplode(',', $userTsConfig['options.']['hideRecords.']['pages'] ?? '', true);
-        $this->backgroundColors = $userTsConfig['options.']['pageTree.']['backgroundColor.'] ?? [];
-        $this->addIdAsPrefix = (bool)($userTsConfig['options.']['pageTree.']['showPageIdWithTitle'] ?? false);
-        $this->addDomainName = (bool)($userTsConfig['options.']['pageTree.']['showDomainNameWithTitle'] ?? false);
-        $this->showMountPathAboveMounts = (bool)($userTsConfig['options.']['pageTree.']['showPathAboveMounts'] ?? false);
-        $backendUserConfiguration = GeneralUtility::makeInstance(BackendUserConfiguration::class);
-        $this->expandedState = $backendUserConfiguration->get('BackendComponents.States.Pagetree');
-        if (is_object($this->expandedState) && is_object($this->expandedState->stateHash)) {
-            $this->expandedState = (array)$this->expandedState->stateHash;
-        } else {
-            $this->expandedState = $this->expandedState['stateHash'] ?: [];
-        }
+        $this->initializeConfiguration();
 
-        // Fetching a part of a pagetree
+        $items = [];
         if (!empty($request->getQueryParams()['pid'])) {
-            $entryPoints = [(int)$request->getQueryParams()['pid']];
+            // Fetching a part of a page tree
+            $entryPoints = $this->getAllEntryPointPageTrees((int)$request->getQueryParams()['pid']);
+            $mountPid = (int)($request->getQueryParams()['mount'] ?? 0);
+            $parentDepth = (int)($request->getQueryParams()['pidDepth'] ?? 0);
+            $this->levelsToFetch = $parentDepth + $this->levelsToFetch;
+            foreach ($entryPoints as $page) {
+                $items = array_merge($items, $this->pagesToFlatArray($page, $mountPid, $parentDepth));
+            }
         } else {
             $entryPoints = $this->getAllEntryPointPageTrees();
+            foreach ($entryPoints as $page) {
+                $items = array_merge($items, $this->pagesToFlatArray($page, (int)$page['uid']));
+            }
+        }
+
+        return new JsonResponse($items);
+    }
+
+    /**
+     * Returns JSON representing page tree filtered by keyword
+     *
+     * @param ServerRequestInterface $request
+     * @return ResponseInterface
+     */
+    public function filterDataAction(ServerRequestInterface $request): ResponseInterface
+    {
+        $searchQuery = $request->getQueryParams()['q'] ?? '';
+        if (trim($searchQuery) === '') {
+            return new JsonResponse([]);
         }
+
+        $this->initializeConfiguration();
+        $this->expandAllNodes = true;
+
         $items = [];
+        $entryPoints = $this->getAllEntryPointPageTrees(0, $searchQuery);
+
         foreach ($entryPoints as $page) {
-            $items = array_merge($items, $this->pagesToFlatArray($page, (int)$page['uid']));
+            if (!empty($page)) {
+                $items = array_merge($items, $this->pagesToFlatArray($page, (int)$page['uid']));
+            }
         }
 
         return new JsonResponse($items);
@@ -254,7 +310,10 @@ class TreeController
 
         $stopPageTree = !empty($page['php_tree_stop']) && $depth > 0;
         $identifier = $entryPoint . '_' . $pageId;
-        $expanded = !empty($page['expanded']) || (isset($this->expandedState[$identifier]) && $this->expandedState[$identifier]);
+        $expanded = !empty($page['expanded'])
+            || (isset($this->expandedState[$identifier]) && $this->expandedState[$identifier])
+            || $this->expandAllNodes;
+
         $backgroundColor = !empty($this->backgroundColors[$pageId]) ? $this->backgroundColors[$pageId] : ($inheritedData['backgroundColor'] ?? '');
 
         $suffix = '';
@@ -309,8 +368,11 @@ class TreeController
                 && $backendUser->checkLanguageAccess(0)
         ];
 
-        if (!empty($page['_children'])) {
+        if (!empty($page['_children']) || $this->getPageTreeRepository()->hasChildren($pageId)) {
             $item['hasChildren'] = true;
+            if ($depth >= $this->levelsToFetch) {
+                $page = $this->getPageTreeRepository()->getTreeLevels($page, 1);
+            }
         }
         if (!empty($prefix)) {
             $item['prefix'] = htmlspecialchars($prefix);
@@ -324,7 +386,7 @@ class TreeController
         if ($icon->getOverlayIcon()) {
             $item['overlayIcon'] = $icon->getOverlayIcon()->getIdentifier();
         }
-        if ($expanded) {
+        if ($expanded && is_array($page['_children']) && !empty($page['_children'])) {
             $item['expanded'] = $expanded;
         }
         if ($backgroundColor) {
@@ -346,9 +408,10 @@ class TreeController
         }
 
         $items[] = $item;
-        if (!$stopPageTree && is_array($page['_children'])) {
+        if (!$stopPageTree && is_array($page['_children']) && !empty($page['_children']) && ($depth < $this->levelsToFetch || $expanded)) {
             $siblingsCount = count($page['_children']);
             $siblingsPosition = 0;
+            $items[key($items)]['loaded'] = true;
             foreach ($page['_children'] as $child) {
                 $child['siblingsCount'] = $siblingsCount;
                 $child['siblingsPosition'] = ++$siblingsPosition;
@@ -358,12 +421,7 @@ class TreeController
         return $items;
     }
 
-    /**
-     * Fetches all entry points for the page tree that the user is allowed to see
-     *
-     * @return array
-     */
-    protected function getAllEntryPointPageTrees(): array
+    protected function getPageTreeRepository(): PageTreeRepository
     {
         $backendUser = $this->getBackendUser();
         $userTsConfig = $backendUser->getTSConfig();
@@ -375,57 +433,94 @@ class TreeController
         }
         $additionalQueryRestrictions[] = GeneralUtility::makeInstance(PagePermissionRestriction::class, GeneralUtility::makeInstance(Context::class)->getAspect('backend.user'), Permission::PAGE_SHOW);
 
-        $repository = GeneralUtility::makeInstance(
+        return GeneralUtility::makeInstance(
             PageTreeRepository::class,
             (int)$backendUser->workspace,
             [],
             $additionalQueryRestrictions
         );
+    }
 
-        $entryPoints = (int)($backendUser->uc['pageTree_temporaryMountPoint'] ?? 0);
-        if ($entryPoints > 0) {
-            $entryPoints = [$entryPoints];
+    /**
+     * Fetches all pages for all tree entry points the user is allowed to see
+     *
+     * @param int $startPid
+     * @param string $query The search query can either be a string to be found in the title or the nav_title of a page or the uid of a page.
+     * @return array
+     */
+    protected function getAllEntryPointPageTrees(int $startPid = 0, string $query = ''): array
+    {
+        $backendUser = $this->getBackendUser();
+        $entryPointId = $startPid > 0 ? $startPid : (int)($backendUser->uc['pageTree_temporaryMountPoint'] ?? 0);
+        if ($entryPointId > 0) {
+            $entryPointIds = [$entryPointId];
         } else {
-            $entryPoints = array_map('intval', $backendUser->returnWebmounts());
-            $entryPoints = array_unique($entryPoints);
-            if (empty($entryPoints)) {
+            //watch out for deleted pages returned as webmount
+            $entryPointIds = array_map('intval', $backendUser->returnWebmounts());
+            $entryPointIds = array_unique($entryPointIds);
+            if (empty($entryPointIds)) {
                 // use a virtual root
                 // the real mount points will be fetched in getNodes() then
                 // since those will be the "sub pages" of the virtual root
-                $entryPoints = [0];
+                $entryPointIds = [0];
             }
         }
-        if (empty($entryPoints)) {
+        if (empty($entryPointIds)) {
             return [];
         }
+        $repository = $this->getPageTreeRepository();
+
+        if ($query !== '') {
+            $this->levelsToFetch = 999;
+            $repository->fetchFilteredTree($query);
+        }
 
-        foreach ($entryPoints as $k => &$entryPoint) {
-            if (in_array($entryPoint, $this->hiddenRecords, true)) {
-                unset($entryPoints[$k]);
+        $entryPointRecords = [];
+        foreach ($entryPointIds as $k => $entryPointId) {
+            if (in_array($entryPointId, $this->hiddenRecords, true)) {
                 continue;
             }
 
             if (!empty($this->backgroundColors) && is_array($this->backgroundColors)) {
                 try {
-                    $entryPointRootLine = GeneralUtility::makeInstance(RootlineUtility::class, $entryPoint)->get();
+                    $entryPointRootLine = GeneralUtility::makeInstance(RootlineUtility::class, $entryPointId)->get();
                 } catch (RootLineException $e) {
                     $entryPointRootLine = [];
                 }
                 foreach ($entryPointRootLine as $rootLineEntry) {
                     $parentUid = $rootLineEntry['uid'];
-                    if (!empty($this->backgroundColors[$parentUid]) && empty($this->backgroundColors[$entryPoint])) {
-                        $this->backgroundColors[$entryPoint] = $this->backgroundColors[$parentUid];
+                    if (!empty($this->backgroundColors[$parentUid]) && empty($this->backgroundColors[$entryPointId])) {
+                        $this->backgroundColors[$entryPointId] = $this->backgroundColors[$parentUid];
                     }
                 }
             }
+            if ($entryPointId === 0) {
+                $entryPointRecord = [
+                    'uid' => 0,
+                    'title' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ?: 'TYPO3'
+                ];
+            } else {
+                $permClause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW);
+                $entryPointRecord = BackendUtility::getRecord('pages', $entryPointId, '*', $permClause);
+
+                if ($entryPointRecord !== null && !$this->getBackendUser()->isInWebMount($entryPointId)) {
+                    $entryPointRecord = null;
+                }
+            }
+            if ($entryPointRecord) {
+                if ($query === '') {
+                    $entryPointRecord = $repository->getTreeLevels($entryPointRecord, $this->levelsToFetch);
+                } else {
+                    $entryPointRecord = $repository->getTree($entryPointRecord['uid'], null, $entryPointIds);
+                }
+            }
 
-            $entryPoint = $repository->getTree($entryPoint, null, $entryPoints);
-            if (!is_array($entryPoint)) {
-                unset($entryPoints[$k]);
+            if (is_array($entryPointRecord) && !empty($entryPointRecord)) {
+                $entryPointRecords[$k] = $entryPointRecord;
             }
         }
 
-        return $entryPoints;
+        return $entryPointRecords;
     }
 
     /**
diff --git a/typo3/sysext/backend/Classes/Tree/Repository/PageTreeRepository.php b/typo3/sysext/backend/Classes/Tree/Repository/PageTreeRepository.php
index d4d6ed45dc6c93c76a9a9593a6c43de7779b95c2..ea5f25f12b996b459b0644f51a67d9be5039001d 100644
--- a/typo3/sysext/backend/Classes/Tree/Repository/PageTreeRepository.php
+++ b/typo3/sysext/backend/Classes/Tree/Repository/PageTreeRepository.php
@@ -17,11 +17,15 @@ declare(strict_types=1);
 
 namespace TYPO3\CMS\Backend\Tree\Repository;
 
+use Doctrine\DBAL\Connection;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Database\Query\QueryHelper;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction;
 use TYPO3\CMS\Core\DataHandling\PlainDataResolver;
+use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Versioning\VersionState;
 
@@ -119,6 +123,7 @@ class PageTreeRepository
      *
      * @param int $entryPoint the page ID to fetch the tree for
      * @param callable $callback a callback to be used to check for permissions and filter out pages not to be included.
+     * @param array $dbMounts
      * @return array
      */
     public function getTree(int $entryPoint, callable $callback = null, array $dbMounts = []): array
@@ -155,6 +160,133 @@ class PageTreeRepository
         }
     }
 
+    /**
+     * Get the page tree based on a given page record and a given depth
+     *
+     * @param array $pageTree The page record of the top level page you want to get the page tree of
+     * @param int $depth Number of levels to fetch
+     * @return array An array with page records and their children
+     */
+    public function getTreeLevels(array $pageTree, int $depth): array
+    {
+        $parentPageIds = [$pageTree['uid']];
+        $groupedAndSortedPagesByPid = [];
+        for ($i = 0; $i < $depth; $i++) {
+            if (empty($parentPageIds)) {
+                break;
+            }
+            $pageRecords = $this->getChildPages($parentPageIds);
+
+            $groupedAndSortedPagesByPid = $this->groupAndSortPages($pageRecords, $groupedAndSortedPagesByPid);
+
+            $parentPageIds = array_column($pageRecords, 'uid');
+        }
+        $this->addChildrenToPage($pageTree, $groupedAndSortedPagesByPid);
+        return $pageTree;
+    }
+
+    /**
+     * Get the child pages from the given parent pages
+     *
+     * @param array $parentPageIds
+     * @return array
+     */
+    protected function getChildPages(array $parentPageIds): array
+    {
+        $pageRecords = $this->getChildPageRecords($parentPageIds);
+
+        foreach ($pageRecords as &$pageRecord) {
+            $pageRecord['uid'] = (int)$pageRecord['uid'];
+
+            if ($this->currentWorkspace > 0) {
+                if ((int)$pageRecord['t3ver_state'] === VersionState::MOVE_PLACEHOLDER) {
+                    $liveRecord = BackendUtility::getRecord('pages', $pageRecord['t3ver_move_id']);
+                    $pageRecord['uid'] = (int)$pageRecord['t3ver_move_id'];
+                    $pageRecord['title'] = $liveRecord['title'];
+                }
+
+                if ((int)$pageRecord['t3ver_oid'] > 0) {
+                    $liveRecord = BackendUtility::getRecord('pages', $pageRecord['t3ver_oid']);
+
+                    $pageRecord['uid'] = (int)$pageRecord['t3ver_oid'];
+                    $pageRecord['pid'] = (int)$liveRecord['pid'];
+                }
+            }
+        }
+        unset($pageRecord);
+
+        return $pageRecords;
+    }
+
+    /**
+     * Retrieve the page records based on the given parent page ids
+     *
+     * @param array $parentPageIds
+     * @return array
+     */
+    protected function getChildPageRecords(array $parentPageIds): array
+    {
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable('pages');
+        $queryBuilder->getRestrictions()
+            ->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+            ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->currentWorkspace));
+
+        if (!empty($this->additionalQueryRestrictions)) {
+            foreach ($this->additionalQueryRestrictions as $additionalQueryRestriction) {
+                $queryBuilder->getRestrictions()->add($additionalQueryRestriction);
+            }
+        }
+
+        $pageRecords = $queryBuilder
+            ->select(...$this->fields)
+            ->from('pages')
+            ->where(
+                $queryBuilder->expr()->eq('sys_language_uid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)),
+                $queryBuilder->expr()->in('pid', $queryBuilder->createNamedParameter($parentPageIds, Connection::PARAM_INT_ARRAY))
+            )
+            ->execute()
+            ->fetchAll();
+
+        // This is necessary to resolve all IDs in a workspace
+        if ($this->currentWorkspace !== 0 && !empty($pageRecords)) {
+            $livePageIds = array_column($pageRecords, 'uid');
+            // Resolve placeholders of workspace versions
+            $resolver = GeneralUtility::makeInstance(
+                PlainDataResolver::class,
+                'pages',
+                $livePageIds
+            );
+            $resolver->setWorkspaceId($this->currentWorkspace);
+            $resolver->setKeepDeletePlaceholder(false);
+            $resolver->setKeepMovePlaceholder(false);
+            $resolver->setKeepLiveIds(false);
+            $recordIds = $resolver->get();
+
+            if (!empty($recordIds)) {
+                $queryBuilder->getRestrictions()->removeAll();
+                $pageRecords = $queryBuilder
+                    ->select(...$this->fields)
+                    ->from('pages')
+                    ->where(
+                        $queryBuilder->expr()->in('uid', $queryBuilder->createNamedParameter($recordIds, Connection::PARAM_INT_ARRAY))
+                    )
+                    ->execute()
+                    ->fetchAll();
+            }
+        }
+
+        return $pageRecords;
+    }
+
+    public function hasChildren(int $pid): bool
+    {
+        $pageRecords = $this->getChildPageRecords([$pid]);
+
+        return !empty($pageRecords);
+    }
+
     /**
      * Fetch all non-deleted pages, regardless of permissions. That's why it's internal.
      *
@@ -313,4 +445,210 @@ class PageTreeRepository
         }
         return [];
     }
+
+    /**
+     * Retrieve the page tree based on the given search filter
+     *
+     * @param string $searchFilter
+     * @return array
+     */
+    public function fetchFilteredTree(string $searchFilter): array
+    {
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable('pages');
+        $queryBuilder->getRestrictions()
+            ->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
+
+        if ($this->getBackendUser()->workspace === 0) {
+            $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(WorkspaceRestriction::class));
+        }
+
+        if (!empty($this->additionalQueryRestrictions)) {
+            foreach ($this->additionalQueryRestrictions as $additionalQueryRestriction) {
+                $queryBuilder->getRestrictions()->add($additionalQueryRestriction);
+            }
+        }
+
+        $expressionBuilder = $queryBuilder->expr();
+
+        $queryBuilder = $queryBuilder
+            ->select(...$this->fields)
+            ->from('pages')
+            ->where(
+                // Only show records in default language
+                $expressionBuilder->eq('sys_language_uid', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
+            );
+
+        $queryBuilder->where(
+            QueryHelper::stripLogicalOperatorPrefix($this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW))
+        );
+        $searchParts = $expressionBuilder->orX();
+        if (is_numeric($searchFilter) && $searchFilter > 0) {
+            $searchParts->add(
+                $expressionBuilder->eq('uid', $queryBuilder->createNamedParameter($searchFilter, \PDO::PARAM_INT))
+            );
+        }
+        $searchFilter = '%' . $queryBuilder->escapeLikeWildcards($searchFilter) . '%';
+
+        $searchWhereAlias = $expressionBuilder->orX(
+            $expressionBuilder->like(
+                'nav_title',
+                $queryBuilder->createNamedParameter($searchFilter, \PDO::PARAM_STR)
+            ),
+            $expressionBuilder->like(
+                'title',
+                $queryBuilder->createNamedParameter($searchFilter, \PDO::PARAM_STR)
+            )
+        );
+        $searchParts->add($searchWhereAlias);
+
+        $queryBuilder->andWhere($searchParts);
+        $pageRecords = $queryBuilder->execute()
+            ->fetchAll();
+
+        $pages = [];
+        foreach ($pageRecords as $page) {
+            $pages[$page['uid']] = $page;
+        }
+
+        if ($this->getBackendUser()->workspace !== 0) {
+            foreach (array_unique(array_column($pages, 't3ver_oid')) as $t3verOid) {
+                if ($t3verOid !== 0) {
+                    unset($pages[$t3verOid]);
+                }
+            }
+        }
+        unset($pageRecords);
+
+        $pages = $this->filterPagesOnMountPoints($pages, $this->getAllowedMountPoints());
+
+        $groupedAndSortedPagesByPid = $this->groupAndSortPages($pages);
+
+        $this->fullPageTree = [
+            'uid' => 0,
+            'title' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ?: 'TYPO3'
+        ];
+        $this->addChildrenToPage($this->fullPageTree, $groupedAndSortedPagesByPid);
+
+        return $this->fullPageTree;
+    }
+
+    /**
+     * Filter all records outside of the allowed mount points
+     *
+     * @param array $pages
+     * @param array $mountPoints
+     * @return array
+     */
+    protected function filterPagesOnMountPoints(array $pages, array $mountPoints): array
+    {
+        foreach ($pages as $key => $pageRecord) {
+            $rootline = BackendUtility::BEgetRootLine(
+                $pageRecord['uid'],
+                '',
+                $this->getBackendUser()->workspace != 0,
+                $this->fields
+            );
+            $rootline = array_reverse($rootline);
+            if (!in_array(0, $mountPoints, true)) {
+                $isInsideMountPoints = false;
+                foreach ($rootline as $rootlineElement) {
+                    if (in_array((int)$rootlineElement['uid'], $mountPoints, true)) {
+                        $isInsideMountPoints = true;
+                        break;
+                    }
+                }
+                if (!$isInsideMountPoints) {
+                    unset($pages[$key]);
+                    //skip records outside of the allowed mount points
+                    continue;
+                }
+            }
+
+            $inFilteredRootline = false;
+            $amountOfRootlineElements = count($rootline);
+            for ($i = 0; $i < $amountOfRootlineElements; ++$i) {
+                $rootlineElement = $rootline[$i];
+                $rootlineElement['uid'] = (int)$rootlineElement['uid'];
+                $isInWebMount = false;
+                if ($rootlineElement['uid'] > 0) {
+                    $isInWebMount = (int)$this->getBackendUser()->isInWebMount($rootlineElement);
+                }
+
+                if (!$isInWebMount
+                    || ($rootlineElement['uid'] === (int)$mountPoints[0]
+                        && $rootlineElement['uid'] !== $isInWebMount)
+                ) {
+                    continue;
+                }
+                if ($this->getBackendUser()->isAdmin() ||($rootlineElement['uid'] === $isInWebMount
+                        && in_array($rootlineElement['uid'], $mountPoints, true))
+                ) {
+                    $inFilteredRootline = true;
+                }
+                if (!$inFilteredRootline) {
+                    continue;
+                }
+
+                if (!isset($pages[$rootlineElement['uid']])) {
+                    $pages[$rootlineElement['uid']] = $rootlineElement;
+                }
+            }
+        }
+        // Make sure the mountpoints show up in page tree even when parent pages are not accessible pages
+        foreach ($mountPoints as $mountPoint) {
+            if ($mountPoint !== 0) {
+                if (!array_key_exists($mountPoint, $pages)) {
+                    $pages[$mountPoint] = BackendUtility::getRecord('pages', $mountPoint);
+                }
+                $pages[$mountPoint]['pid'] = 0;
+            }
+        }
+
+        return $pages;
+    }
+
+    /**
+     * Group pages by parent page and sort pages based on sorting property
+     *
+     * @param array $pages
+     * @param array $groupedAndSortedPagesByPid
+     * @return array
+     */
+    protected function groupAndSortPages(array $pages, $groupedAndSortedPagesByPid = []): array
+    {
+        foreach ($pages as $key => $pageRecord) {
+            $parentPageId = (int)$pageRecord['pid'];
+            $sorting = (int)$pageRecord['sorting'];
+            while (isset($groupedAndSortedPagesByPid[$parentPageId][$sorting])) {
+                $sorting++;
+            }
+            $groupedAndSortedPagesByPid[$parentPageId][$sorting] = $pageRecord;
+        }
+
+        return $groupedAndSortedPagesByPid;
+    }
+
+    /**
+     * Get allowed mountpoints. Returns temporary mountpoint when temporary mountpoint is used
+     * @return array
+     */
+    protected function getAllowedMountPoints(): array
+    {
+        $mountPoints = (int)($this->getBackendUser()->uc['pageTree_temporaryMountPoint'] ?? 0);
+        if (!$mountPoints) {
+            $mountPoints = array_map('intval', $this->getBackendUser()->returnWebmounts());
+            return array_unique($mountPoints);
+        }
+        return [$mountPoints];
+    }
+
+    /**
+     * @return BackendUserAuthentication
+     */
+    protected function getBackendUser(): BackendUserAuthentication
+    {
+        return $GLOBALS['BE_USER'];
+    }
 }
diff --git a/typo3/sysext/backend/Configuration/Backend/AjaxRoutes.php b/typo3/sysext/backend/Configuration/Backend/AjaxRoutes.php
index a3955dc73411db08f09c8ba86f7a4d0af04602a3..37053a5033dbca5952fb53217707b0931da589bc 100644
--- a/typo3/sysext/backend/Configuration/Backend/AjaxRoutes.php
+++ b/typo3/sysext/backend/Configuration/Backend/AjaxRoutes.php
@@ -95,6 +95,12 @@ return [
         'target' => Controller\Page\TreeController::class . '::fetchDataAction'
     ],
 
+    // Get data for page tree
+    'page_tree_filter' => [
+        'path' => '/page/tree/filterData',
+        'target' => Controller\Page\TreeController::class . '::filterDataAction'
+    ],
+
     // Get page tree configuration
     'page_tree_configuration' => [
         'path' => '/page/tree/fetchConfiguration',
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/PageTree/PageTree.js b/typo3/sysext/backend/Resources/Public/JavaScript/PageTree/PageTree.js
index bd7c8da35a6bbb33b40fe86cc072a5c19c6dcad9..9ce08e22355e343136c286e6a09c5bf684ee2c6e 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/PageTree/PageTree.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/PageTree/PageTree.js
@@ -32,12 +32,14 @@ define(['jquery',
      */
     var PageTree = function() {
       SvgTree.call(this);
+      this.originalNodes = [];
       this.settings.defaultProperties = {
         hasChildren: false,
         nameSourceField: 'title',
         prefix: '',
         suffix: '',
         locked: false,
+        loaded: false,
         overlayIcon: '',
         selectable: true,
         expanded: false,
@@ -51,6 +53,7 @@ define(['jquery',
     };
 
     PageTree.prototype = Object.create(SvgTree.prototype);
+
     var _super_ = SvgTree.prototype;
 
     /**
@@ -204,6 +207,16 @@ define(['jquery',
       return this.nodes[0];
     };
 
+    /**
+     * Finds node by its stateIdentifier (e.g. "0_360")
+     * @return {Node}
+     */
+    PageTree.prototype.getNodeByIdentifier = function(identifier) {
+      return this.nodes.find(function (node) {
+        return node.stateIdentifier === identifier;
+      });
+    };
+
     /**
      * Observer for the selectedNode event
      *
@@ -282,10 +295,59 @@ define(['jquery',
     };
 
     PageTree.prototype.showChildren = function(node) {
+      this.loadChildrenOfNode(node);
       _super_.showChildren(node);
       Persistent.set('BackendComponents.States.Pagetree.stateHash.' + node.stateIdentifier, 1);
     };
 
+    /**
+     * Loads child nodes via Ajax (used when expanding a collapesed node)
+     *
+     * @param parentNode
+     * @return {boolean}
+     */
+    PageTree.prototype.loadChildrenOfNode = function(parentNode) {
+      if (parentNode.loaded) {
+        return;
+      }
+      var _this = this;
+      _this.nodesAddPlaceholder();
+      d3.json(_this.settings.dataUrl + '&pid=' + parentNode.identifier + '&mount=' + parentNode.mountPoint + '&pidDepth=' + parentNode.depth, function(error, json) {
+          if (error) {
+            var title = TYPO3.lang.pagetree_networkErrorTitle;
+            var desc = TYPO3.lang.pagetree_networkErrorDesc;
+
+            if (error && error.target && (error.target.status || error.target.statusText)) {
+              title += ' - ' + (error.target.status || '') + ' ' + (error.target.statusText || '');
+            }
+
+            Notification.error(
+              title,
+              desc);
+
+            _this.nodesRemovePlaceholder();
+            throw error;
+          }
+
+          var nodes = Array.isArray(json) ? json : [];
+          //first element is a parent
+          nodes.shift();
+          var index = _this.nodes.indexOf(parentNode) + 1;
+          //adding fetched node after parent
+          nodes.forEach(function (node, offset) {
+            _this.nodes.splice(index + offset, 0, node);
+          });
+
+          parentNode.loaded = true;
+          _this.setParametersNode();
+          _this.prepareDataForVisibleNodes();
+          _this.update();
+          _this.nodesRemovePlaceholder();
+          _this.switchFocusNode(parentNode);
+        });
+
+    };
+
     PageTree.prototype.updateNodeBgClass = function(nodeBg) {
       return _super_.updateNodeBgClass.call(this, nodeBg).call(this.dragDrop.drag());
     };
@@ -388,6 +450,51 @@ define(['jquery',
         });
     };
 
+    PageTree.prototype.filterTree = function(searchQuery) {
+      var _this = this;
+      _this.nodesAddPlaceholder();
+
+      d3.json(_this.settings.filterUrl + '&q=' + searchQuery, function(error, json) {
+        if (error) {
+          var title = TYPO3.lang.pagetree_networkErrorTitle;
+          var desc = TYPO3.lang.pagetree_networkErrorDesc;
+
+          if (error && error.target && (error.target.status || error.target.statusText)) {
+            title += ' - ' + (error.target.status || '') + ' ' + (error.target.statusText || '');
+          }
+
+          Notification.error(
+            title,
+            desc);
+
+          _this.nodesRemovePlaceholder();
+          throw error;
+        }
+
+        var nodes = Array.isArray(json) ? json : [];
+        if (nodes.length > 0) {
+          if (_this.originalNodes.length === 0) {
+            _this.originalNodes = JSON.stringify(_this.nodes);
+          }
+          _this.replaceData(nodes);
+        }
+        _this.nodesRemovePlaceholder();
+      });
+    };
+
+    PageTree.prototype.resetFilter = function() {
+        if (this.originalNodes.length > 0) {
+          var currentlySelected = this.getSelectedNodes()[0];
+          this.nodes = JSON.parse(this.originalNodes);
+          this.originalNodes = '';
+          if (currentlySelected) {
+            this.selectNode(this.getNodeByIdentifier(currentlySelected.stateIdentifier));
+          }
+        } else {
+          this.refreshTree();
+        }
+    };
+
     PageTree.prototype.setTemporaryMountPoint = function(pid) {
       var params = 'pid=' + pid;
       var _this = this;
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/PageTree/PageTreeElement.js b/typo3/sysext/backend/Resources/Public/JavaScript/PageTree/PageTreeElement.js
index 56b0f3160e3b4dea0c7074367bfac2db20eac0c5..3ca16ece4bf0b8e29ea7c04a9be46531c8b6dfd5 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/PageTree/PageTreeElement.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/PageTree/PageTreeElement.js
@@ -71,11 +71,14 @@ define(['jquery',
         });
 
         var dataUrl = top.TYPO3.settings.ajaxUrls.page_tree_data;
+        var filterUrl = top.TYPO3.settings.ajaxUrls.page_tree_filter;
+
         var configurationUrl = top.TYPO3.settings.ajaxUrls.page_tree_configuration;
 
         $.ajax({url: configurationUrl}).done(function(configuration) {
           tree.initialize($element.find('#typo3-pagetree-tree'), $.extend(configuration, {
             dataUrl: dataUrl,
+            filterUrl: filterUrl,
             showIcons: true
           }));
 
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/PageTree/PageTreeToolbar.js b/typo3/sysext/backend/Resources/Public/JavaScript/PageTree/PageTreeToolbar.js
index 83cb935dc497e3f09a6f2af942e8fda6836d3647..5a6185c22165fc9074d8a09791b79154ebda1cfa 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/PageTree/PageTreeToolbar.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/PageTree/PageTreeToolbar.js
@@ -66,13 +66,6 @@ define(['jquery',
        * @type {jQuery}
        */
       this.template = null;
-
-      /**
-       * Nodes stored encoded before tree gets filtered
-       *
-       * @type {string}
-       */
-      this.originalNodes = '';
     };
 
     /**
@@ -182,7 +175,9 @@ define(['jquery',
           if (input) {
             input.clearable({
               onClear: function (input) {
-                $(input).trigger('input');
+                _this.tree.resetFilter();
+                _this.tree.prepareDataForVisibleNodes();
+                _this.tree.update();
               }
             });
           }
@@ -204,8 +199,10 @@ define(['jquery',
         }
       });
 
-      $toolbar.find(this.settings.searchInput).on('input', function() {
-        _this.search.call(_this, this);
+      $toolbar.find(this.settings.searchInput).on('keypress', function(e) {
+        if(e.keyCode === 13 || e.which === 13) {
+          _this.search.call(_this, this);
+        }
       });
 
       $toolbar.find('[data-toggle="tooltip"]').tooltip();
@@ -232,29 +229,11 @@ define(['jquery',
     TreeToolbar.prototype.search = function(input) {
       var _this = this;
       var name = $(input).val().trim();
-
       if (name !== '') {
-        if (this.originalNodes.length === 0) {
-          this.originalNodes = JSON.stringify(this.tree.nodes);
-        }
-
-        this.tree.nodes[0].expanded = false;
-        this.tree.nodes.forEach(function (node) {
-          var regex = new RegExp(name, 'i');
-          if (node.identifier.toString() === name || regex.test(node.name) || regex.test(node.alias || '')) {
-            _this.showParents(node);
-            node.expanded = true;
-            node.hidden = false;
-          } else if (node.depth !== 0) {
-            node.hidden = true;
-            node.expanded = false;
-          }
-        });
+        _this.tree.filterTree(name);
       } else {
-        this.tree.nodes = JSON.parse(this.originalNodes);
-        this.originalNodes = '';
+        _this.tree.resetFilter();
       }
-
       this.tree.prepareDataForVisibleNodes();
       this.tree.update();
     };
diff --git a/typo3/sysext/backend/Tests/Functional/Controller/Page/TreeControllerTest.php b/typo3/sysext/backend/Tests/Functional/Controller/Page/TreeControllerTest.php
index e066862f370ddb7ae470544b193a58f57fe1b666..f7e32822bf8c46535dccc96ea39ce4cba5e3c760 100644
--- a/typo3/sysext/backend/Tests/Functional/Controller/Page/TreeControllerTest.php
+++ b/typo3/sysext/backend/Tests/Functional/Controller/Page/TreeControllerTest.php
@@ -160,26 +160,7 @@ class TreeControllerTest extends FunctionalTestCase
                             [
                                 'uid' => 1520,
                                 'title' => 'Forecasts',
-                                '_children' => [
-                                    [
-                                        'uid' => 1521,
-                                        'title' => 'Current Year',
-                                        '_children' => [
-                                        ],
-                                    ],
-                                    [
-                                        'uid' => 1522,
-                                        'title' => 'Next Year',
-                                        '_children' => [
-                                        ],
-                                    ],
-                                    [
-                                        'uid' => 1523,
-                                        'title' => 'Five Years',
-                                        '_children' => [
-                                        ],
-                                    ],
-                                ],
+                                '_children' => [],
                             ],
                             [
                                 'uid' => 1530,
@@ -230,6 +211,67 @@ class TreeControllerTest extends FunctionalTestCase
         self::assertEquals($expected, $actual);
     }
 
+    /**
+     * @test
+     */
+    public function getSubtreeForAccessiblePage()
+    {
+        $actual = $this->subject->_call('getAllEntryPointPageTrees', 1200);
+        $keepProperties = array_flip(['uid', 'title', '_children']);
+        $actual = $this->sortTreeArray($actual);
+        $actual = $this->normalizeTreeArray($actual, $keepProperties);
+
+        $expected = [
+            [
+                'uid' => 1200,
+                'title' => 'EN: Features',
+                '_children' => [
+                    [
+                        'uid' => 1210,
+                        'title' => 'EN: Frontend Editing',
+                        '_children' => [
+                        ],
+                    ],
+                    [
+                        'uid' => 1230,
+                        'title' => 'EN: Managing content',
+                        '_children' => [
+                        ],
+                    ],
+                ],
+            ],
+        ];
+        self::assertEquals($expected, $actual);
+    }
+
+    /**
+     * @test
+     */
+    public function getSubtreeForNonAccessiblePage()
+    {
+        $actual = $this->subject->_call('getAllEntryPointPageTrees', 1510);
+        $keepProperties = array_flip(['uid', 'title', '_children']);
+        $actual = $this->sortTreeArray($actual);
+        $actual = $this->normalizeTreeArray($actual, $keepProperties);
+
+        $expected = [];
+        self::assertEquals($expected, $actual);
+    }
+
+    /**
+     * @test
+     */
+    public function getSubtreeForPageOutsideMountPoint()
+    {
+        $actual = $this->subject->_call('getAllEntryPointPageTrees', 7000);
+        $keepProperties = array_flip(['uid', 'title', '_children']);
+        $actual = $this->sortTreeArray($actual);
+        $actual = $this->normalizeTreeArray($actual, $keepProperties);
+
+        $expected = [];
+        self::assertEquals($expected, $actual);
+    }
+
     /**
      * @test
      */
@@ -271,14 +313,7 @@ class TreeControllerTest extends FunctionalTestCase
                             [
                                 'uid' => 1240,
                                 'title' => 'EN: Managing data',
-                                '_children' => [
-                                    [
-                                        'uid' => 124010,
-                                        'title' => 'EN: Managing complex data',
-                                        '_children' => [
-                                        ],
-                                    ],
-                                ],
+                                '_children' => [],
                             ],
                             [
                                 'uid' => 1210,
@@ -313,26 +348,7 @@ class TreeControllerTest extends FunctionalTestCase
                             [
                                 'uid' => 1520,
                                 'title' => 'Forecasts',
-                                '_children' => [
-                                    [
-                                        'uid' => 1521,
-                                        'title' => 'Current Year',
-                                        '_children' => [
-                                        ],
-                                    ],
-                                    [
-                                        'uid' => 1522,
-                                        'title' => 'Next Year',
-                                        '_children' => [
-                                        ],
-                                    ],
-                                    [
-                                        'uid' => 1523,
-                                        'title' => 'Five Years',
-                                        '_children' => [
-                                        ],
-                                    ],
-                                ],
+                                '_children' => [],
                             ],
                             [
                                 'uid' => 1530,
@@ -351,13 +367,7 @@ class TreeControllerTest extends FunctionalTestCase
                                 // from pid 1510 (missing permissions) to pid 1700 (visible now)
                                 'uid' => 1511,
                                 'title' => 'Products',
-                                '_children' => [
-                                    [
-                                        'uid' => 151110,
-                                        'title' => 'Product 1',
-                                        '_children' => [],
-                                    ]
-                                ],
+                                '_children' => [],
                             ],
                         ],
                     ],
@@ -396,6 +406,39 @@ class TreeControllerTest extends FunctionalTestCase
         self::assertEquals($expected, $actual);
     }
 
+    /**
+     * @test
+     */
+    public function getSubtreeForAccessiblePageInWorkspace()
+    {
+        $actual = $this->subject->_call('getAllEntryPointPageTrees', 1200);
+        $keepProperties = array_flip(['uid', 'title', '_children']);
+        $actual = $this->sortTreeArray($actual);
+        $actual = $this->normalizeTreeArray($actual, $keepProperties);
+
+        $expected = [
+            [
+                'uid' => 1200,
+                'title' => 'EN: Features',
+                '_children' => [
+                    [
+                        'uid' => 1210,
+                        'title' => 'EN: Frontend Editing',
+                        '_children' => [
+                        ],
+                    ],
+                    [
+                        'uid' => 1230,
+                        'title' => 'EN: Managing content',
+                        '_children' => [
+                        ],
+                    ],
+                ],
+            ],
+        ];
+        self::assertEquals($expected, $actual);
+    }
+
     /**
      * @param int $workspaceId
      */
diff --git a/typo3/sysext/core/Resources/Private/Language/locallang_core.xlf b/typo3/sysext/core/Resources/Private/Language/locallang_core.xlf
index bca830fd11044fea04225781194e5ed1d45325e9..9527969fb920ff49c1ee111fe6061bd6f7681c46 100644
--- a/typo3/sysext/core/Resources/Private/Language/locallang_core.xlf
+++ b/typo3/sysext/core/Resources/Private/Language/locallang_core.xlf
@@ -1072,7 +1072,7 @@ Do you want to refresh it now?</source>
 				<source>Filter tree</source>
 			</trans-unit>
 			<trans-unit id="tree.searchTermInfo" resname="tree.searchTermInfo">
-				<source>Enter search term</source>
+				<source>Enter search term and hit enter</source>
 			</trans-unit>
 			<trans-unit id="warning.install_password" resname="warning.install_password">
 				<source>The Install Tool is still using the default password "joh316". Update this within the %sAbout section%s of the Install Tool.</source>