---
title: "ModifyProfileQueryEvent"
manual: "Academic Profiles"
version: "main"
permalink: "https://docs.typo3.org/permalink/fgtclb/academic-persons:developers-event-modify-profile-query-event@main"
source: "Developers/Events/ModifyProfileQueryEvent.rst"
rendered: "2026-10-15T10:24:29+00:00"
---

# ModifyProfileQueryEvent {#developers-event-modify-profile-query-event}

## Narrowing what a plugin shows {#developers-query-events}

`\FGTCLB\AcademicPersons\Event\ModifyProfileQueryEvent` and
`\FGTCLB\AcademicPersons\Event\ModifyContractQueryEvent` are dispatched
immediately before a plugin query is executed, and a listener adds conditions
to it. They are the supported way to make a plugin show fewer records than it
would, by a consent flag, a site the profile belongs to or an editorial state.

| Event | Dispatched for |
| --- | --- |
| `ModifyProfileQueryEvent` | the profile query of the list, list-and-detail and card plugins, the letter query of the two list plugins (see [Which letters lead somewhere](https://docs.typo3.org/permalink/fgtclb/academic-persons:developers-letter-availability@main)), and the uid lookup of the selected-profiles plugin |
| `ModifyContractQueryEvent` | the uid lookup of the selected-contracts plugin |

## When it is dispatched {#when-it-is-dispatched}

`ProfileRepository` dispatches the event once for every profile query it
runs for a plugin, after the query settings and the conditions of the plugin
are prepared and before they are applied:

-   **List and list-and-detail plugins**, list action: once for the profiles
    of the list, after [ModifyProfileDemandEvent](https://docs.typo3.org/permalink/fgtclb/academic-persons:developers-event-modify-profile-demand-event@main).
    While the content element shows the letter navigation and no profiles are
    selected by hand, once more for the letters, see
    [Which letters lead somewhere](https://docs.typo3.org/permalink/fgtclb/academic-persons:developers-letter-availability@main).
-   **Card plugin**: once for the profiles the card shows, also after
    `ModifyProfileDemandEvent`.
-   **Selected-profiles plugin**: once for the uid lookup of the selected
    profiles. No demand event precedes it.

The detail view is not covered, see the rules below.
`ModifyPluginViewEvent` follows when the action renders, see
[ModifyPluginViewEvent](https://docs.typo3.org/permalink/fgtclb/academic-persons:developers-event-modify-plugin-view-event@main).

Code that calls `ProfileRepository::findByDemand()` or
`ProfileRepository::findByUids()` outside the plugins dispatches the
event as well, without a plugin context.

## What a listener reads and changes {#what-a-listener-reads-and-changes}

| Method | What it does |
| --- | --- |
| `getQuery()` | The Extbase `QueryInterface` of the profiles, to build the conditions on. |
| `addConstraint()` | Adds a condition. Every listener may add any number of them. |
| `getConstraints()` | The conditions the listeners added so far. |
| `getDemand()` | The list demand the query is built from, after the listeners of `ModifyProfileDemandEvent`. `null` for the uid lookup of the selected-profiles plugin, which knows no demand. |
| `getPluginControllerActionContext()` | The plugin the query is rendered for, see [The plugin action context](https://docs.typo3.org/permalink/fgtclb/academic-persons:developers-plugin-action-context@main). `null` where the query has no plugin behind it. |

The context carries the plugin name, the settings of the content element, the
request, the site, the language and the content object.
[ModifyContractQueryEvent](https://docs.typo3.org/permalink/fgtclb/academic-persons:developers-event-modify-contract-query-event@main) has the same methods
apart from `getDemand()`.

## Example {#example}

A site that lists only the profiles of persons who agreed to be published
keeps the agreement in a column of its own, here `public_display_consent`
on the profile table. The listener restricts the two list plugins to those
profiles and leaves the card and the selected profiles alone, which an editor
fills by hand:

**EXT:my_extension/Classes/EventListener/ShowOnlyConsentingProfiles.php**

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\EventListener;

use FGTCLB\AcademicPersons\Event\ModifyProfileQueryEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;

final class ShowOnlyConsentingProfiles
{
    #[AsEventListener(identifier: 'my-extension/show-only-consenting-profiles')]
    public function __invoke(ModifyProfileQueryEvent $event): void
    {
        // The two list plugins only. The card plugin of the same page keeps everything.
        if (!in_array($event->getPluginControllerActionContext()?->getPluginName(), ['List', 'ListAndDetail'], true)) {
            return;
        }
        $query = $event->getQuery();
        $event->addConstraint($query->equals('publicDisplayConsent', true));
    }
}
```

`getPluginName()` is the name a plugin is **registered** with, not its
content element type. The six names are listed in
[The plugin names](https://docs.typo3.org/permalink/fgtclb/academic-persons:developers-plugin-names@main). The letter query of the list carries the same
context as the list query, so the listener narrows the letters in the same
way.

## Rules and pitfalls {#rules-and-pitfalls}

The collected conditions are combined with a logical **AND** with the ones the
extension builds itself: the storage folders, the organisational unit and
function type filters of the content element, the letter filter, hidden
records and the language handling. They are applied **before pagination**.
A *constraint* therefore only ever narrows a result, and it cannot drop a
condition the extension put there.

> [!WARNING]
> That guarantee covers the constraints, and nothing else about the query.
>
> `setOrderings()` is called on the query *after* the event, so a
> listener that sets an ordering is overwritten without a notice. The
> ordering belongs to the plugin: it is what the editor chose in the content
> element, and a listener that changed it would override that choice for
> every element at once.
>
> A condition set with `matching()` instead of `addConstraint()` is
> **folded into the same AND** rather than dropped, so it narrows like any
> other. Use `addConstraint()` anyway: `matching()` replaces what
> is on the query, so two listeners doing it leave only the last one's
> condition standing, while `addConstraint()` collects.
>
> The **query settings, the limit and the offset are live**.
> `getQuerySettings()` hands out the object the query is parsed from,
> and it is read after this event, so a listener that calls
> `setRespectStoragePage(false)` or `setIgnoreEnableFields(true)`
> on it widens the result past the content element, and a listener that
> calls `setLimit()` cuts it. Nothing guards any of this. It is the same
> object because a listener needs it to build an expression in the first
> place. Add constraints and leave the rest of the query alone.

The detail view is not covered: neither event is dispatched for it. It resolves
its profile through Extbase argument mapping, and the one finder it does use,
for a hidden profile while the plugin's "show hidden records" is on, builds
its own query and never goes through the shared path these events sit in. So a
profile a listener hides from the lists is still reachable through its own
detail URL. A condition that has to hold there as well belongs in an access
check, not in these events.

A listener that answers the list query and the letter query differently makes
the letter navigation offer letters that lead to an empty list, or hide letters
that do not. Answer both calls alike.

A project that narrowed the plugins by subclassing or XCLASSing the
repositories moves that code here, see
[Breaking: The plugins call different repository finders](https://docs.typo3.org/permalink/fgtclb/academic-persons:breaking-profile-and-contract-finder-signatures@main).

## Introduced in {#introduced-in}

-   [Feature: Narrow the profiles and contracts a plugin shows](https://docs.typo3.org/permalink/fgtclb/academic-persons:feature-profile-and-contract-query-events@main) (2.4) introduced the
    event.
-   [Feature: The query events say which plugin is asking](https://docs.typo3.org/permalink/fgtclb/academic-persons:feature-ace-715-academic-persons-query-event-context@main) added the
    plugin context in 3.0.
-   [Breaking: The plugins call different repository finders](https://docs.typo3.org/permalink/fgtclb/academic-persons:breaking-profile-and-contract-finder-signatures@main) describes the
    finders that hand the context over.
-   [Feature: The letter navigation knows which letters lead somewhere](https://docs.typo3.org/permalink/fgtclb/academic-persons:feature-letter-navigation-availability@main) dispatches it for the
    letters as well.
-   [Important: The profile queries see a letter switching off the pagination](https://docs.typo3.org/permalink/fgtclb/academic-persons:important-1790607226@main) makes the list query see a letter switching
    the pagination off.
