The storagePid: where Extbase looks for records 

Every Extbase repository query — except findByUid() and findByIdentifier() — is restricted to records stored on specific TYPO3 pages, the storage pages, configured through the storagePid. This is not an optional filter you opt into: it is a constraint Extbase adds to the query automatically, alongside the language and visibility constraints.

Because it is applied silently, the storagePid is the setting that most often makes a query behave differently from what you expect — too few records, too many, or the wrong ones. This page collects the whole story: how the value is resolved, how to widen it to subpages, and how to override or drop it for a single query.

storagePid as a query constraint 

When a repository builds a query, Extbase adds a WHERE condition limiting the result to records whose pid is one of the configured storage pages. The only built-in methods that skip it are findByUid() and findByIdentifier(), which look a record up by its unique identifier regardless of page.

This matters for two reasons. First, a repository with no configured storagePid queries page 0 and finds nothing — see Why storagePid = 0 does not disable the restriction. Second, the constraint is resolved from several configuration sources that override one another, so the page a query actually searches is not always the one you set.

The storagePid resolution chain in the frontend 

Several options feed the storagePid, and later ones override earlier ones. In a frontend request Extbase resolves them in this order, depending on the controller that handles the request:

  1. Framework TypoScript config.tx_extbase.persistence.storagePid. This is the framework-level default that applies to every Extbase plugin, and the same key a backend module reads (see below). When nothing sets it, the frontend default is 0.
  2. Extension plugin TypoScript plugin.tx_myextension.persistence.storagePid overrides the framework value for all plugins of one extension.
  3. Plugin-specific TypoScript plugin.tx_myextension_conferencelist.persistence.storagePid overrides the extension-wide value for that one plugin.
  4. The Startingpoint field on a plugin's content element. When an editor fills in the Behaviour > Starting point field, the chosen pages override the TypoScript value. The label hides the real database column, which is pages — useful to know when inspecting records or writing overrides, as the label is not guaranteed to read the same on every instance or in every language.
  5. FlexForm — only if a plugin's FlexForm defines a field bound to persistence.storagePid. A FlexForm overrides the storage page only if it deliberately exposes the persistence sheet. This is mentioned for completeness — it would be unusual to expose a persistence.storagePid FlexForm field on a plugin that already has a pages (Startingpoint) field.
  6. PHP, per query — query settings override everything above. Where in your code you set them decides how wide the effect is: inside a single repository method only that one query changes; inside an overridden createQuery() every query the repository builds is affected. See Overriding the storagePid for a single query.

The most specific option that supplies a value is applied; a specific query's settings always have the final say.

The storagePid resolution chain in a backend module 

An Extbase repository behaves the same way inside a backend module as it does in the frontend: every query is restricted to the configured storage pages. What differs is how the storagePid is resolved. A backend module is not a content element on a page, so there is no plugin record, no FlexForm and no Starting point field to draw from. The chain is therefore much shorter:

  1. Framework TypoScript config.tx_extbase.persistence.storagePid. Note the config scope: a backend module reads the same framework-level key the frontend uses for its Extbase configuration, not a plugin scope.
  2. The current page — if nothing above sets a storagePid, Extbase falls back to the page the module is currently showing, taken from the id request parameter (the page selected in the page tree). When no page is selected — or the module has no page tree at all, as many backend modules do not — this falls back to 0.
  3. Module TypoScript module.tx_myextension.persistence.storagePid, overridden by the module-specific module.tx_myextension_mymodule.persistence.storagePid. This is the backend counterpart of the plugin.tx_* scope used in the frontend, and the usual place to set a storagePid for a module.
  4. PHP, per query — query settings override everything above, exactly as in the frontend. See Overriding the storagePid for a single query.
EXT:my_extension/ext_typoscript_setup.typoscript
module.tx_myextension.persistence {
    storagePid = 42
    # Include records stored one level below page 42
    recursive = 1
}
Copied!

Module TypoScript is conventionally registered in the extension's ext_typoscript_setup.typoscript, as shown above, so the configuration is global and does not depend on which page the module happens to be viewing. This is the recommended placement, see module TypoScript reference.

The recursive setting works the same in a backend module as in the frontend, as described next.

Searching subpages with the recursive setting 

By default, Extbase searches only the storage pages you configured — not their subpages. To include records stored in subfolders below the storage page, set a recursion depth.

In TypoScript, persistence.recursive applies to the TypoScript-configured storagePids:

EXT:my_extension/Configuration/Sets/MyExtension/setup.typoscript
plugin.tx_myextension.persistence {
    storagePid = 42
    # Include records stored up to two levels below page 42
    recursive = 2
}
Copied!

The Starting point field has its own Recursive selector next to it that does the same for the editor-chosen pages.

Overriding the storagePid for a single query 

The configured storagePid is the default for every query a repository builds. To change it for one query, use the query settings object inside a custom repository method:

EXT:my_extension/Classes/Domain/Repository/ConferenceRepository.php
<?php

namespace MyVendor\MyExtension\Domain\Repository;

use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
use TYPO3\CMS\Extbase\Persistence\Repository;

class ConferenceRepository extends Repository
{
    public function findAllAcrossPages(): QueryResultInterface
    {
        $query = $this->createQuery();
        $query->getQuerySettings()->setRespectStoragePage(false);
        return $query->execute();
    }
}
Copied!

Two settings control the page restriction:

Name Type Default
bool true
int[]

setRespectStoragePage(bool)

setRespectStoragePage(bool)
Type
bool
Default
true

When false, the storagePid restriction is dropped entirely and the query searches the whole table regardless of page. This is how you disable the page restriction.

setStoragePageIds(array)

setStoragePageIds(array)
Type
int[]

Sets the storage pages explicitly for this query, overriding the configured storagePid. Expects an array of integer page UIDs. Has no effect once setRespectStoragePage(false) is set.

The query settings object also controls language, enable fields and deleted records, which are independent of the storagePid:

Why storagePid = 0 does not disable the restriction 

A common misconception is that setting the storagePid to 0 switches off the page restriction. It does not. For an ordinary table, 0 is the UID of the page tree root, a level no editor ever stores records on — so the query looks for records on page 0 and finds none. The effect is an empty result, not an unrestricted query.

To search the whole table irrespective of page, drop the restriction in PHP instead:

EXT:my_extension/Classes/Domain/Repository/ConferenceRepository.php
<?php

namespace MyVendor\MyExtension\Domain\Repository;

use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
use TYPO3\CMS\Extbase\Persistence\Repository;

class ConferenceRepository extends Repository
{
    public function findEverywhere(): QueryResultInterface
    {
        $query = $this->createQuery();
        // Search the whole table, ignoring the configured storage page
        $query->getQuerySettings()->setRespectStoragePage(false);
        return $query->execute();
    }
}
Copied!

Building the storagePid array from editor input 

When for any reason the storagePid array can't be built by Extbase but you want to do it manually, use Core API rather than constructing the recursive page list yourself. The core \TYPO3\CMS\Core\Domain\Repository\PageRepository resolves the recursive list of page UIDs for you, honouring access rights and the enable-field rules:

EXT:my_extension/Classes/Domain/Repository/ConferenceRepository.php
<?php

namespace MyVendor\MyExtension\Domain\Repository;

use TYPO3\CMS\Core\Domain\Repository\PageRepository;
use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
use TYPO3\CMS\Extbase\Persistence\Repository;

class ConferenceRepository extends Repository
{
    public function __construct(
        protected readonly PageRepository $pageRepository,
    ) {
        parent::__construct();
    }

    /**
     * @param int[] $startPages Pages chosen by an editor or by plugin settings
     */
    public function findInPagesRecursive(array $startPages, int $depth): QueryResultInterface
    {
        $storagePageIds = $this->pageRepository->getPageIdsRecursive($startPages, $depth);

        $query = $this->createQuery();
        $query->getQuerySettings()->setStoragePageIds($storagePageIds);
        return $query->execute();
    }
}
Copied!

The resulting integer array is exactly what setStoragePageIds() expects. Letting the core API build it keeps the recursion logic identical to what Extbase applies internally.