---
title: "Backend relation scoping"
manual: "TYPO3 EXT:thuecat"
version: "main"
permalink: "https://docs.typo3.org/permalink/werkraummedia/thuecat:developers-backend-relation-scoping@main"
source: "Developers/BackendRelationScoping.rst"
rendered: "2026-10-09T11:47:03+00:00"
---

# Backend relation scoping {#developers-backend-relation-scoping}

What the scoping does for editors is described in [Backend relation scoping](https://docs.typo3.org/permalink/werkraummedia/thuecat:backend-relation-scoping@main). This page covers
how it is wired and what to decide when a record kind is added.

To achieve a seamless integration, TCA and Flexform are manipulated after being compiled and the
result is then cached. PSR-14 events take care of the manipulation. The final result can be
inspected via the **Sites -> Page TSconfig** and **Configuration -> TCA** backend
modules.

## The criterion {#developers-backend-relation-scoping-criterion}

`\WerkraumMedia\ThueCat\Service\SiteScopedSelectFields` decides which fields are scoped and what
clause they carry. A column matches when it is a `select` field, its `foreign_table` is
listed in `SiteScopedSelectFields::SCOPED_TABLES`, and it is not the table's translation parent
(`ctrl.transOrigPointerField`).

A matching column receives two conditions on the foreign table:

```sql
AND {#<foreign_table>}.{#pid} IN (###PAGE_TSCONFIG_IDLIST###)
AND {#<foreign_table>}.{#sys_language_uid} IN (0, -1)
```

Each condition is appended to the column's existing `foreign_table_where` only when that clause
does not already state it, compared with whitespace removed. Whatever else the clause holds stays
untouched.

## Event listeners {#developers-backend-relation-scoping-listeners}

Three PSR-14 listeners apply the criterion. Each one derives the fields from it rather than from a
list, so the fields carrying the clause and the fields receiving an id list cannot drift apart.

-   **`SiteScopedRelationsTcaListener` on `AfterTcaCompilationEvent`**

    Writes the clause into every matching column of every table in the compiled TCA.

-   **`SiteScopedRelationsFlexFormListener` on `AfterFlexFormDataStructureParsedEvent`**

    Writes the clause into matching fields of each parsed FlexForm sheet, so a content element
    selecting ThueCat records is scoped too. A sheet has no `ctrl`, so no field there is excluded
    as a translation parent.

-   **`SiteScopedRelationsPageTsConfigListener` on `ModifyLoadedPageTsConfigEvent`**

    Supplies the value of `###PAGE_TSCONFIG_IDLIST###`. Core resolves the marker per table and
    field and offers no wildcard, so the listener emits one line per scoped field:

    ```typoscript
    TCEFORM.<table>.<field>.PAGE_TSCONFIG_IDLIST = <ids>
    TCEFORM.<table>.<flexField>.<recordType>.<sheet>.<field>.PAGE_TSCONFIG_IDLIST = <ids>
    ```

    FlexForm field names containing a dot, such as `settings.towns`, are escaped, because
    TSconfig reads the dot as a path separator. The FlexForm data structure is parsed per record
    type; a type whose structure cannot be resolved contributes no lines.

    The ids are the pages of the site holding the current page, the deepest entry of the rootline,
    resolved by `\WerkraumMedia\ThueCat\Service\SitePageIds` — the service the import uses for
    the same question. A page outside any site yields `0`: an unresolved marker would leave the
    clause offering the whole table.

The type-ahead wizard is limited by `TCEFORM.suggest.default.addWhere` in
[`Configuration/page.tsconfig`](https://docs.typo3.org/m/typo3/reference-coreapi/14.3/en-us/ExtensionArchitecture/FileStructure/Configuration/PageTsconfig.html#file-extension-configuration-page-tsconfig), reading the same marker. Its columns are unqualified, because
the wizard queries one table at a time, while the dropdown's query joins and needs the table name.

Core's `SuggestWizardController` ignores a field's `foreign_table_where` while TSconfig
sets an `addWhere`. The language condition is therefore part of the TSconfig block as well: fields
carrying it only in their own `foreign_table_where` would lose it on the wizard while keeping
it on the dropdown.

Category and keyword fields are not part of this mechanism. Their tree start is resolved by the form
data provider `\WerkraumMedia\ThueCat\Typo3\FormDataProvider\AnchorStartingPoints` from the
`###THUECAT_ANCHOR###` marker, see [Category and keyword trees](https://docs.typo3.org/permalink/werkraummedia/thuecat:frontend-output-plugin-settings-trees@main).

## Adding a record kind {#developers-backend-relation-scoping-new-record-kind}

`SCOPED_TABLES` is the statement of what counts as a ThueCat record, and it is maintained by
hand.

**When a new record kind is introduced, decide whether its table belongs in that list.** Nothing
detects the omission: a relation field pointing at a table missing from it keeps working and offers
the records of the whole installation, in every site, on both the dropdown and the suggest wizard.

Adding the table is the usual answer, but not automatic. A table deliberately shared across sites,
or one never used as a relation target, is correctly left out.

Append `\WerkraumMedia\ThueCat\Service\SiteScopedSelectFields::SCOPED_TABLES` with the new
table, this is all it takes.
