---
title: "The profile lists"
manual: "Academic Profiles"
version: "main"
permalink: "https://docs.typo3.org/permalink/fgtclb/academic-persons:developers-profile-list@main"
source: "Developers/ProfileList.rst"
rendered: "2026-10-15T10:24:29+00:00"
---

# The profile lists {#developers-profile-list}

The list and list-and-detail plugins build more than the list itself: the
letters of the letter navigation, the links of the pagination and of the
navigation, and the page cache lifetime. This page describes how these follow
the list, and where [ModifyProfileDemandEvent](https://docs.typo3.org/permalink/fgtclb/academic-persons:developers-event-modify-profile-demand-event@main) and
[ModifyProfileQueryEvent](https://docs.typo3.org/permalink/fgtclb/academic-persons:developers-event-modify-profile-query-event@main) reach them.

## Which letters lead somewhere {#developers-letter-availability}

The letter navigation of the list and list-and-detail plugins is told which
letters lead to a list that is not empty:
`ProfileRepository::findAlphabetFilterLetters()`, assigned to the view as
`alphabetFilterLetters`, the letters `a` to `z`, each mapped to a boolean. It
is assigned only while the content element has the navigation switched on and
no profiles are selected by hand. A manual selection ignores the letter filter.

The letters come from the list's own query with the letter cleared, so the
active letter never narrows the others, and both events reach it:
`ModifyProfileDemandEvent` and `ModifyProfileQueryEvent` are
dispatched **twice** for a list that shows the navigation, once for the list
and once for its letters, and the query event carries the same plugin context
both times. A listener that narrows the list narrows the letters in the same way, as
long as it answers both calls alike. The demand of the second call carries no
letter.

While the content element shows only contracts valid today,
`ModifyProfileDemandEvent` is dispatched once more, for the next day on which
the list changes because a contract that meets its conditions starts or ends.
That day caps the page cache lifetime, so a listener that adds a condition on
contracts should answer this call like the others.

Two things the letters do not see, both shared with the list's own pagination
count, which is computed in SQL as well:

-   A listener of `ModifyPluginViewEvent` that assigns other profiles to
    the view. The letters are computed from the query, not from the records
    the view renders.
-   In a workspace preview, a profile deleted or hidden only in the workspace.
    It still makes its letter available. Live, the letters are exact.

The method compares with the letter filter's own predicate, `LIKE` or
`ILIKE` on PostgreSQL against the last name, so a name is available
under exactly the letter the list files it under. Where a name starting with an
umlaut ends up is a question of the database collation: under O on MariaDB and
MySQL, under no letter on PostgreSQL and SQLite.

## What the navigation links carry {#developers-navigation-links}

The list action of the list and list-and-detail plugins assigns
`activeListArguments`, the visitor's choices the pagination and the letter
navigation carry into their links. For the templates, see
[what the navigation links carry](https://docs.typo3.org/permalink/fgtclb/academic-persons:templates-navigation-links@main). Which demand properties a visitor may set is one list in
`ProfileController`, and it decides both what the property mapping accepts
from the request and what the links carry, so the two cannot differ. Today it
holds the page, the letter, the view mode and the two visitor filters. A value
equal to its default is left out.

The view mode is resolved before that: the list action checks the requested
mode against the switch of the content element and the allowed modes, and
writes the result back into the demand. It is empty for the default mode and
for a mode it rejected, so neither ever reaches a link. The mode the list renders is
also read before the events, so a demand a listener hands back cannot name a
partial.

The filters are checked before that as well. A value that is not a whole
number never reaches the property mapping, and the list action resets a
function type or unit that is not one of the options of the filter to `0`,
which includes every value while the content element does not offer the
filter. `ProfileDemand::getFunctionTypeFilter()` and
`getOrganisationalUnitFilter()` therefore hold an offered uid in the
default language, or `0`, when a listener of `ModifyProfileDemandEvent`
receives the demand. A value the listener sets itself is applied as it is.

The value is read from the mapped demand before any event of the list is
dispatched, `ModifyProfileDemandEvent` and `ModifyProfileQueryEvent`
alike. A listener acts again on the request a link leads to, so what it changes
need not travel in the URL.
