konradmichalik / typo3-pagetree-facets
Pagetree Facets - Filterable page tree with an extensible filter tab API, built on the TYPO3 v14 BeforePageTreeIsFilteredEvent.
Package info
github.com/konradmichalik/typo3-pagetree-facets
Type:typo3-cms-extension
pkg:composer/konradmichalik/typo3-pagetree-facets
Requires
- php: ~8.3.0 || ~8.4.0 || ~8.5.0
- ext-ctype: *
- ext-mbstring: *
- doctrine/dbal: ^4.4
- psr/event-dispatcher: ^1.0
- psr/http-message: ^1.0 || ^2.0
- typo3/cms-backend: ^14.0
- typo3/cms-core: ^14.0
Requires (Dev)
- eliashaeussler/version-bumper: ^4.0.3
- konradmichalik/ttt: ^0.4.0
- phpunit/phpcov: ^11.0
- phpunit/phpunit: ^11.0 || ^12.0 || ^13.0
- typo3/cms-base-distribution: ^14.0
- typo3/cms-seo: ^14.0
- typo3/testing-framework: ^9.0 || ^10.0
This package is auto-updated.
Last update: 2026-08-07 12:24:30 UTC
README
TYPO3 extension typo3_pagetree_facets
This extension turns the TYPO3 backend page tree into a faceted filter. Instead of scrolling through a large tree, you narrow it down to exactly the pages you care about: by content type, page state, records, activity, translations or SEO.
Filters are compact tokens that you can type into the tree's existing search field or assemble in a guided modal, and the whole feature is extensible through a public filter tab API.
✨ Features
- Filterable page tree — type tokens into the tree's search field, or open a guided modal with Ctrl/Cmd+Shift+L
- Eight built-in filter tabs — content elements, records, activity, page type, layouts, page state, translations and SEO, plus
site:/under:scopes - Sharable links, session persistence and favorites — hand a filter to a colleague, keep it across a reload, or save it under a name
- Extensible — add a single option to an existing tab, or a whole tab of your own
- Per-user/group control — disable tabs installation-wide or via User TSconfig
- Raw query escape hatch (
raw:, opt-in) — match arbitraryfield=valueconditions against any TCA table the user may already read
🔥 Installation
Requirements
- TYPO3 ^14.0
- PHP 8.3 - 8.5
Composer
composer require konradmichalik/typo3-pagetree-facets
TER
Download the zip file from TYPO3 extension repository (TER).
📖 How it works
Press Ctrl/Cmd+Shift+L (or use the toolbar button next to the tree's search field) to open the filter modal. Pick criteria by clicking through the tabs on the left; each selection appears as a removable chip above the tree, with a per-tab count of matching pages, and narrows the tree live as you go.
Prefer typing? The Token view toggle (top bar) swaps the freetext field for the full filter phrase, kept in two-way sync with the form — edit either side and the other follows. Note that editing the form re-serialises the phrase, so tokens the form cannot represent survive only while you stay in the field.
Under the hood, every filter is a compact token that lands in the tree's existing search field, so you can also skip the modal and type directly:
doktype:1 is:empty # standard pages without content
table:tx_news_domain_model_news # pages containing news records
ce:uploads updated:<30d # pages with an uploads CE, touched last 30 days
seo:missing-description # indexable pages without meta description
Whitespace means AND, a comma means OR within one criterion (doktype:1,4).
Freetext without a key: prefix behaves like the core title/UID search, and
unknown tokens are ignored.
| Tab | Token keys |
|---|---|
| Content elements | ce: |
| Records | table: record: text: |
| Activity | updated: created: by: createdby: |
| Page type | doktype: |
| Layouts | layout: pagelayout: |
| Page state | is: |
| Translations | untranslated: translated: |
| SEO (requires EXT:seo) | seo: |
| Scopes | site:<identifier> under:<uid> |
Important
Every criterion resolves to pages, whatever it matches on. ce:uploads or
table:tx_news_domain_model_news do not list content elements or news records —
they narrow the tree to the pages those records live on. The result of a filter
is always a set of pages.
Matching pages are marked in the tree with a narrow colour stripe — the same one the core's own title search uses. A filtered tree shows the matches plus the branches leading down to them, so the stripe is what tells an actual hit from a parent that is only there to hold it. Hover a node to read the reason in its tooltip.
Note
This is not the global backend search (the toolbar magnifier / Cmd/Ctrl+K). That one finds individual records, pages and modules and jumps you to them; this extension narrows the page tree to the pages matching structured criteria. Two different jobs — use the toolbar search to locate one thing, this to reshape the tree.
⚙️ Configuration
Extension settings
You can find the extension settings in the TYPO3 backend under
Admin Tools > Settings > Extension Configuration > typo3_pagetree_facets.
| Setting | Default | Description |
|---|---|---|
adminOnly |
0 |
Only administrators can use the filter modal and tokens. |
disabledTabs |
(empty) | Comma-separated list of built-in tab identifiers to disable installation-wide. |
persistFilter |
0 |
Remember each backend user's current page tree filter for their session, so it survives a reload or module switch (cleared on logout). |
emptyResultNotice |
1 |
Show a hint below the page tree when a filter matches nothing, offering to adjust or reset it. |
enableRawQueryTab |
0 |
Enable the raw: power-user token (see below). Off by default. |
Built-in tab identifiers: records, ce, activity, doktype, layout, state,
translations, seo, raw (only registered at all when enableRawQueryTab is on).
Note
Disabling a tab also makes its token keys unknown to the filter engine, so the restriction cannot be bypassed by typing the token into the search field manually.
Note
The Activity tab's "Edited by" / "Created by" picker searches backend user
names. For non-admins this requires be_users among the group's allowed
tables (Tables (listing) / tables_select) — without that grant the picker
offers no suggestions (filtering by a known uid, e.g. by:3, still works).
The raw: power-user token
Syntax: raw:<table>|<field>=<value>|<field2>=<value2>..., e.g.
raw:tt_content|CType=image|hidden=0 (* for LIKE matching). Off by default —
it matches arbitrary fields on any table the current backend user can already
select records from, so review your backend groups' table permissions before
enabling it.
Field names are whitelisted against the table's TCA columns, plus uid, which
has no columns entry but is the most obvious thing to look a record up by:
raw:tt_content|uid=201 narrows the tree to the page holding that element.
Unknown field names are dropped rather than matched.
User TSconfig
Both restrictions can also be applied per backend user or group:
# Disable the extension entirely for this user/group
tx_typo3pagetreefacets.disable = 1
# Disable individual tabs (merged with the disabledTabs extension setting)
tx_typo3pagetreefacets.disableTabs = seo, translations
⚠️ Known limitations & assumptions
- Scopes are applied as a post-filter.
site:<identifier>andunder:<uid>do not restrict the query up front; they filter the already-matched UID set by resolving each page's rootline. This is intentional — it avoids materializing a whole site/subtree — and is cheap for the narrow result sets a token filter normally produces. A very broad single criterion combined only with a scope (e.g.is:empty site:mainon an installation with thousands of empty pages) resolves one rootline per matched page; pair it with a narrower criterion if it ever feels slow. layout:matches the layout set on the page itself, not the effective one. A page that leavesbackend_layoutempty and only inherits a parent'sbackend_layout_next_levelis not a match. Resolving inheritance would mean walking the rootline for every candidate page, which does not scale on large trees.backend_layout_next_levelhas no token of its own — "what this page uses" and "what this page hands down" are separate questions, and one token answering both would make a hit ambiguous. The layouts offered in the modal are collected from every site root (plus the global level) and deduplicated, so layouts defined only in the page TSconfig of a subtree below a site root do not appear as options — the token still matches them if you type it.pagelayout:is the same tab's second criterion and matches the frontend layout field (pages.layout) instead; its0("Default") is the column default and therefore offered as no checkbox, thoughpagelayout:0still resolves if typed.- Page permissions are enforced by the core, not this extension. Tabs resolve
page UIDs installation-wide; the core page tree then intersects that set with
the backend user's
PAGE_SHOWpermission clause and mount points, so the tree never reveals pages the user may not see. - Freetext combined with a token is resolved by this extension, not the core.
Pure freetext (no
key:prefix) is handed to the core unchanged, so it keeps the full core behaviour — title/nav_title, translated titles and frontend-URI resolution. Once a freetext word shares the phrase with a keyed token (e.g.doktype:1 home), the extension resolves it itself so it can intersect it with the other criteria: aLIKEacross all searchablepagesfields plus a numeric UID match. That set is broader than the core's title/nav_titlesearch but does not cover translated titles orhttp(s)://frontend URIs — search for those on their own, without a token.
🔌 Extending
There are two extension points, and the smaller one is usually the one you want:
- A single option in an existing tab — one more value under a token key that
already exists, e.g. another checkbox in Page state's
is:group:FilterOptionInterface+RegisterFilterOptionsEvent. - A whole tab — own token keys, own modal UI:
FilterTabInterface+RegisterFilterTabsEvent.
The built-ins use the exact same two paths; there is no private shortcut.
→ example_tab
is a minimal extension in this repository that exercises both, commented method by
method. Its README walks through the interfaces, the priority semantics and what
counts as public API.
🙏 Acknowledgments
This project is inspired by the great pagetreefilter extension.
🧑💻 Contributing
Please have a look at CONTRIBUTING.md.
⭐ License
This project is licensed under GNU General Public License 2.0 (or later).

