---
title: "Glossary API"
manual: "Glossary 2"
version: "main"
permalink: "https://docs.typo3.org/permalink/jweiland/glossary2:glossary-api@main"
source: "Developer/API/Index.rst"
rendered: "2026-11-03T17:28:49+00:00"
---

# Glossary API {#glossary-api}

Since glossary2 4.0.0 we deliver a new Glossary API which you can use to
implement a Glossary Index (A-Z list) into your own extension. All the magic
you need you'll find in GlossaryService class.

## Build a glossary with the API {#glossary-api-build}

In class `GlossaryService` you will find public method called `buildGlossary`
which you have to use. As our API does not know the table to use and does not
know further WHERE conditions, it is up to you to deliver a TYPO3 QueryBuilder
or QueryResult instance as first argument.

<!-- TODO: no Markdown rendering for "versionchanged" -->

The public API of GlossaryService uses generic types. It accepts
queries and query results of any model, not only of the glossary2 model.

We prefer creating a new method into your Repository and return an Extbase
QueryResult object:

**EXT:my_extension/Classes/Domain/Repository/MyRepository.php**

```php
/**
 * @return QueryResultInterface<int, MyModel>
 */
public function getQueryResultToFindAllEntries(): QueryResultInterface
{
    return $this->createQuery()->execute();
}
```

Alternative you can also return a TYPO3 QueryBuilder instance. BUT: It's up
to you now to respect storage PIDs, translation and workspaces:

**EXT:my_extension/Classes/Domain/Repository/MyRepository.php**

```php
public function getQueryBuilderToFindAllEntries(): QueryBuilder
{
    $table = 'tx_myext_domain_model_whatever';
    $query = $this->createQuery();
    $queryBuilder = $this->connectionPool->getQueryBuilderForTable($table);
    $queryBuilder->setRestrictions(
        GeneralUtility::makeInstance(FrontendRestrictionContainer::class),
    );

    // Do not set any SELECT, ORDER BY, GROUP BY statement.
    // It will be set by glossary2 API
    $queryBuilder
        ->from($table)
        ->andWhere(
            $queryBuilder->expr()->in(
                'pid',
                $queryBuilder->createNamedParameter(
                    $query->getQuerySettings()->getStoragePageIds(),
                    ArrayParameterType::INTEGER,
                ),
            ),
        );

    return $queryBuilder;
}
```

Within your controller you can call our API that way:

**EXT:my_extension/Classes/Controller/MyController.php**

```php
/**
 * @param string $letter Show only records starting with this letter
 */
#[Extbase\Validate(['validator' => 'String', 'param' => 'letter'])]
#[Extbase\Validate([
    'validator' => 'StringLength',
    'param' => 'letter',
    'options' => ['minimum' => 0, 'maximum' => 3],
])]
public function listAction(string $letter = ''): ResponseInterface
{
    $companies = $this->companyRepository->findByStartingLetter(
        $letter,
        $this->settings,
    );

    $this->view->assign('companies', $companies);
    $this->view->assign(
        'glossar',
        $this->glossaryService->buildGlossary(
            $this->myRepository->getQueryResultToFindAllEntries(),
            [],
            $this->request,
        ),
    );

    return $this->htmlResponse();
}
```

This will transfer the fully rendered HTML Glossar to View.

Please always pass the current Extbase request as third argument. It is
needed by `f:link.action` to build the A-Z links and to read the TypoScript
settings of glossary2 (`plugin.tx_glossary2.settings.templatePath`).
Use `f:format.raw()` in Fluid Template:

**EXT:my_extension/Resources/Private/Templates/My/List.html**

```html
{glossar -> f:format.raw()}
```

## Configure the glossary API {#glossary-api-configure}

If you want, you can configure our API with second `options` argument:

**EXT:my_extension/Classes/Controller/MyController.php**

```php
$this->view->assign(
    'glossar',
    $this->glossaryService->buildGlossary(
        $this->myRepository->getQueryResultToFindAllEntries(),
        [
            'settings' => $this->settings,
            'templatePath' => 'EXT:myext/Resources/Private/Templates/Glossary.html',
        ],
        $this->request,
    ),
);
```

-   **templatePath**

    -   *Type:* string
    -   *Default:* EXT:glossary2/Resources/Private/Templates/Glossary.html

    All rendering of the Glossary is in one file. There is no configuration for
    partial-, template- nor for layoutRootPaths.

-   **settings**

    -   *Type:* array
    -   *Default:* empty

    If you have your own Template defined, it may be useful to have your own
    settings inside of your template. Assign your own settings or use settings
    of your controller (see example above).

-   **extensionName**

    -   *Type:* string
    -   *Default:* glossary2

    Please change default value to extension name of your extension. This is
    needed for correct linking of the A-Z list. It will be used within the
    Plugin namespace in URI: `tx_extensionname_pluginname`. Underscores will
    automatically be converted to UpperCamelCase.

    It's your part to check the GET parameters (letter) and adapt your queries
    to show filtered records.

-   **pluginName**

    -   *Type:* string
    -   *Default:* glossar

    Please change default value to plugin name of your extension. This is needed
    for correct linking of the A-Z list. It will be used within the Plugin
    namespace in URI: `tx_extensionname_pluginname`.

    It's your part to check the GET parameters (letter) and adapt your queries
    to show filtered records.

-   **controllerName**

    -   *Type:* string
    -   *Default:* Glossar

    Please change default value to controller name which should be used for
    links. It will be used as controller part in URI:
    `tx_extensionname_pluginname[controllerName]`.

    It's your part to check the GET parameters (letter) and adapt your queries
    to show filtered records.

-   **actionName**

    -   *Type:* string
    -   *Default:* list

    Please change default value to action name of given controller name above,
    which should be used for links. It will be used as action part
    in URI: `tx_extensionname_pluginname[actionName]`.

    It's your part to check the GET parameters (letter) and adapt your queries
    to show filtered records.

-   **mergeNumbers**

    -   *Type:* bool
    -   *Default:* true

    By default the numbers will be represented in A-Z list as 0-9 instead
    of 0 1 2 3 4 5 6 7 8 9.

-   **column**

    -   *Type:* string
    -   *Default:* title

    The column of your QueryBuilder to extract the first letters from.

-   **columnAlias**

    -   *Type:* string
    -   *Default:* Letter

    To prevent duplicate usage of column in GROUP BY and ORDER BY we are working
    with an column alias. If you already have a column called `Letter` in your
    table you should change that property to something unique.

-   **possibleLetters**

    -   *Type:* string
    -   *Default:* 0-9,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z

    If empty it uses the default from ExtensionSettings

    These are the allowed letters to be shown in frontend. So, if you remove for
    example the letter `r`, it will not be shown in frontend, regardless if a
    record starting with `r` is in ResultSet or not.

    If you disable `mergeNumbers` you have to add each individual number here
    instead of using 0-9.

    It is not allowed to use a combination of numbers and a range like:
    0, 1-3, 4. It is not allowed to use ranges other that 0-9 like: 0-3, 4-9.

## Extend your controller {#glossary-api-controller}

It is up to you to process the letter in your controller. In most cases you
may extend your listAction:

**EXT:my_extension/Classes/Controller/MyController.php**

```php
public function listAction(string $letter = ''): ResponseInterface
{
    if ($letter) {
        $myRecords = $this->myRepo->findByLetter($letter);
    } else {
        $myRecords = $this->myRepo->findAll();
    }
    $this->view->assign('myRecords', $myRecords);

    return $this->htmlResponse();
}
```

## Extend your repository {#glossary-api-repository}

Above we have used a new method `findByLetter`. With glossary2 4.1.0 you can
use our API with Extbase Query or Doctrine.

### Example for an Extbase query {#glossary-api-repository-extbase}

**EXT:my_extension/Classes/Domain/Repository/MyRepository.php**

```php
public function findByLetter(string $letter): QueryResultInterface
{
    $glossaryService = GeneralUtility::makeInstance(GlossaryService::class);
    $query = $this->createQuery();

    $constraints = [];
    $constraints[] = $glossaryService->getLetterConstraintForExtbaseQuery(
        $query,
        'myColumnName',
        $letter,
    );

    return $query->matching($query->logicalAnd(...$constraints))->execute();
}
```

### Example for a Doctrine query {#glossary-api-repository-doctrine}

**EXT:my_extension/Classes/Domain/Repository/MyRepository.php**

```php
public function findByLetter(string $letter): QueryResultInterface
{
    $glossaryService = GeneralUtility::makeInstance(GlossaryService::class);
    $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
        ->getQueryBuilderForTable('my_table');
    $queryBuilder
        ->select('*')
        ->from('my_table')
        ->andWhere(
            $glossaryService->getLetterConstraintForDoctrineQuery(
                $queryBuilder,
                'my_colum_name',
                $letter,
            ),
        );

    $query = $this->createQuery();
    return $query->statement($queryBuilder)->execute();
}
```

## Extend the glossary function {#glossary-api-extend}

We have added two PSR-14 events to extend functionality of glossary API.
Register your event listeners with the PHP attribute `#[AsEventListener]`.
See [PSR-14 events](https://docs.typo3.org/permalink/jweiland/glossary2:events@main) for all events of glossary2.

### PostProcessFirstLettersEvent {#glossary-api-postprocessfirstletters}

After retrieving the possible first letters from database, we clean, sort and
remove duplicates. The result will then be sent to the event
`JWeilandGlossary2EventPostProcessFirstLettersEvent`. Use
`getFirstLetters()` to get the letters as simple array:

```php
$firstLetters = [
    0 => '0',
    1 => 'a',
    2 => 'b',
    3 => 'c',
    ...
];
```

Add or remove letters as you like and set the modified array with
`setFirstLetters()`:

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

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\EventListener;

use JWeiland\Glossary2\Event\PostProcessFirstLettersEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;

#[AsEventListener('my-extension/remove-letter-q')]
final readonly class RemoveLetterEventListener
{
    public function __invoke(PostProcessFirstLettersEvent $event): void
    {
        $event->setFirstLetters(
            array_values(array_diff($event->getFirstLetters(), ['q'])),
        );
    }
}

```

<!-- TODO: no Markdown rendering for "versionadded" -->

The method setFirstLetters() was added. Before, the modified letters
could not be passed back to glossary2.

### SanitizeValueForCharsetHelperEvent {#glossary-api-modifylettermapping}

After retrieving the possible first letters from database, we start a
cleaning process of each individual letter. The letter is converted to
lower case and special chars are mapped to their ASCII representation with
the `CharsetConverter` of TYPO3, for example german umlauts ÄÖÜ to AOU. Only
the first character of the result is used.

If you need further mappings like for french or spain you can use the event
`JWeilandGlossary2EventSanitizeValueForCharsetHelperEvent`. Get the
pre-sanitized value with `getValue()` and set your mapped value with
`setValue()`:

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

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\EventListener;

use JWeiland\Glossary2\Event\SanitizeValueForCharsetHelperEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;

#[AsEventListener('my-extension/map-letters')]
final readonly class MapLetterEventListener
{
    private const LETTER_MAPPING = [
        'ł' => 'l',
        'đ' => 'd',
    ];

    public function __invoke(SanitizeValueForCharsetHelperEvent $event): void
    {
        $event->setValue(strtr($event->getValue(), self::LETTER_MAPPING));
    }
}

```
