---
title: "Extension points"
manual: "Academic Base"
version: "main"
permalink: "https://docs.typo3.org/permalink/fgtclb/academic-base:developers-extension-points@main"
source: "Developers/ExtensionPoints/Index.rst"
rendered: "2026-10-17T08:47:46+00:00"
---

# Extension points {#developers-extension-points}

This page is the contract between the academic extensions and the code of a
project. It covers every academic extension and **category_types**,
and it is a complete list: what it names is public API, and what it does not
name is not.

Every class, interface, trait and enum listed here carries the `@api`
tag in its docblock, so the promise is visible in the code as well. A test of
the extensions' own repository makes sure the tags and this page name the
same classes.

-   [What the promise means](https://docs.typo3.org/permalink/fgtclb/academic-base:what-the-promise-means@main)
-   [What is public API](https://docs.typo3.org/permalink/fgtclb/academic-base:what-is-public-api@main)
-   [What is not public API](https://docs.typo3.org/permalink/fgtclb/academic-base:what-is-not-public-api@main)
-   [Events](https://docs.typo3.org/permalink/fgtclb/academic-base:events@main)
-   [What an event hands over](https://docs.typo3.org/permalink/fgtclb/academic-base:what-an-event-hands-over@main)
-   [Interfaces](https://docs.typo3.org/permalink/fgtclb/academic-base:interfaces@main)
-   [Services and classes a project names](https://docs.typo3.org/permalink/fgtclb/academic-base:services-and-classes-a-project-names@main)
-   [Base classes](https://docs.typo3.org/permalink/fgtclb/academic-base:base-classes@main)
-   [Traits](https://docs.typo3.org/permalink/fgtclb/academic-base:traits@main)
-   [Domain models](https://docs.typo3.org/permalink/fgtclb/academic-base:domain-models@main)
-   [The extension points every extension should have](https://docs.typo3.org/permalink/fgtclb/academic-base:the-extension-points-every-extension-should-have@main)

## What the promise means {#developers-extension-points-promise}

Public API does not change silently. A release that breaks or deprecates
anything listed on this page says so in the changelog of the extension:

-   a **Breaking** entry when existing code stops working, with the
    migration;
-   a **Deprecation** entry when the old way keeps working until the
    next major version, and says what replaces it.

Anything else may change in any release, a bugfix release included, and
without a changelog entry. Code that relies on it has to be checked on every
update.

## What is public API {#developers-extension-points-api}

-   The [events](https://docs.typo3.org/permalink/fgtclb/academic-base:developers-extension-points-events@main), and the
    [types they hand to a listener](https://docs.typo3.org/permalink/fgtclb/academic-base:developers-extension-points-types@main).
-   The [interfaces](https://docs.typo3.org/permalink/fgtclb/academic-base:developers-extension-points-interfaces@main).
-   The [services and classes](https://docs.typo3.org/permalink/fgtclb/academic-base:developers-extension-points-services@main) a
    project names in its configuration or its code.
-   The [base class](https://docs.typo3.org/permalink/fgtclb/academic-base:developers-extension-points-base-classes@main) of a
    profile factory.
-   The two [traits](https://docs.typo3.org/permalink/fgtclb/academic-base:developers-extension-points-traits@main) of
    **academic_base** for Extbase controllers.
-   The [domain models](https://docs.typo3.org/permalink/fgtclb/academic-base:developers-extension-points-models@main): their
    public getters, which the templates read, and extending them to add fields.
-   Templates, partials and sections: their paths below
    `Resources/Private/`, the names of their sections, the variables
    they receive, and the ViewHelpers they call, by tag name and arguments.
    The **Templates** chapter of each extension describes how to
    override them.
-   Settings: the site settings of the site sets, the TypoScript constants and
    settings, and the settings of the content elements.
-   The TypoScript and page TSconfig keys the extensions document.
-   The keys of the language files, which a site overrides labels by.
-   The identifiers `academic-persons/apply-settings-to-tca` and
    `academic-jobs/apply-settings-to-tca` of the listeners that apply the
    persons and the jobs settings to the compiled TCA, so that a listener of
    `\TYPO3\CMS\Core\Configuration\Event\AfterTcaCompilationEvent` can
    be ordered after them. The listener classes are not public API.
-   The format of `Configuration/CategoryTypes.yaml`, see the
    [category types chapter of category_types](https://docs.typo3.org/p/fgtclb/category-types/main/en-us/Developers/CategoryTypes/Index.html).
-   The routing aspect type `CategoryFilterMapper` of
    **category_types** and its settings, see the [routing chapter of
    category_types](https://docs.typo3.org/p/fgtclb/category-types/main/en-us/Developers/Routing/Index.html).
    The class behind it is not public API.
-   The format of `Configuration/FrontendIcons.php`, the frontend icon
    registry of **academic_base**: the format of
    [`Configuration/Icons.php`](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/FileStructure/Configuration/Icons.html#file-extension-configuration-icons-php), read from every active package, and a
    package loaded later replaces an identifier, see
    [Frontend icons](https://docs.typo3.org/permalink/fgtclb/academic-base:configuration-frontend-icons@main).
-   The view helper `icon` of **academic_base**, in the namespace
    `http://typo3.org/ns/FGTCLB/AcademicBase/ViewHelpers`, by tag name and
    arguments, which renders an icon of the frontend icon registry, and the
    identifier `default-not-found` of the icon it renders for an unknown
    identifier. The class behind it is not public API.
-   The view helper `frontendIconMap` of **academic_base**, in the
    same namespace, by tag name and arguments, and the JSON data block it
    renders: the element `<script type="application/json"
    data-academic-icons>`, its `data-academic-icons-*` attributes and the
    object of identifier and markup it holds, see
    [Icons in frontend JavaScript](https://docs.typo3.org/permalink/fgtclb/academic-base:icons-frontend-javascript@main). Which icons it serves is part of the
    contract, see [Which icons are served](https://docs.typo3.org/permalink/fgtclb/academic-base:icons-frontend-served@main). The class behind it is not
    public API.
-   The icon endpoint of **academic_base**, `_academic/icons.json`
    below the base of every site and site language: its path, its
    parameters, the JSON object it answers, its status codes and its caching
    headers, see [The icon endpoint](https://docs.typo3.org/permalink/fgtclb/academic-base:icons-frontend-endpoint@main). The middleware behind it is
    not public API.
-   The JavaScript module `@fgtclb/academic-base/frontend/icons.js` of
    **academic_base** and its exports, `IconFactory`,
    `endpointFrom()`, `Sizes`, `Size`, `IconEndpoint` and
    `IDENTIFIER_PATTERN`, see [The icon factory](https://docs.typo3.org/permalink/fgtclb/academic-base:icons-frontend-factory@main).
-   The view helper `ct:categoryTypeTitle` of **category_types**,
    by tag name and arguments, which names a category type in a template by
    its registered title, see [naming a type in a template](https://docs.typo3.org/p/fgtclb/category-types/main/en-us/Developers/CategoryTypes/Index.html#developers-category-types-title).
    The class behind it is not public API.

## What is not public API {#developers-extension-points-not-api}

Everything else. In particular:

-   **Controllers.** Every plugin controller is `final`, so neither a
    subclass nor an XCLASS of one loads. A plugin is extended through its
    events: every plugin action that renders a view dispatches the
    [plugin view event](https://docs.typo3.org/permalink/fgtclb/academic-base:developers-extension-points-plugin-view@main), except
    the profile editing of **academic_persons_edit**, which offers
    each of its writes to an event instead. The partner, program and project
    lists and the program finder dispatch a demand and a list event as well,
    and the B-ITE job list a request and a result event.
-   **Repositories.** A condition a plugin should apply belongs in a demand or
    a query event, not in an XCLASS of the repository.
-   **Services, data processors, ViewHelper classes, backend item providers,
    hooks, event listeners, commands and upgrade wizards**, except the few
    [a project names](https://docs.typo3.org/permalink/fgtclb/academic-base:developers-extension-points-services@main). A service
    is replaced through the container where it is registered behind one of the
    interfaces below, never by subclassing it.
-   **Everything marked** `@internal`. The marker says in the code what
    the absence of `@api` says for the rest.

An XCLASS is unsupported for every class but a domain model, listed or not: a
listed class keeps what it promises to a caller, not what it offers to a
subclass. The [upgrade check](https://docs.typo3.org/permalink/fgtclb/academic-base:upgrade-check-configuration@main) reports such
an XCLASS as a warning, and as an error when the class is final or gone. How
to extend a model is described under [Domain models](https://docs.typo3.org/permalink/fgtclb/academic-base:developers-extension-points-models@main).

## Events {#developers-extension-points-events}

Every event is a `final` class, dispatched through the PSR-14 event
dispatcher of TYPO3. A listener registers for it with the
`#[AsEventListener]` attribute of TYPO3. The developer chapters of
[academic_bite_jobs](https://docs.typo3.org/p/fgtclb/academic-bite-jobs/main/en-us/Developers/Index.html),
[academic_contacts4pages](https://docs.typo3.org/p/fgtclb/academic-contacts4pages/main/en-us/Developers/Index.html),
[academic_persons](https://docs.typo3.org/p/fgtclb/academic-persons/main/en-us/Developers/Index.html),
[academic_persons_edit](https://docs.typo3.org/p/fgtclb/academic-persons-edit/main/en-us/Developers/Index.html),
[academic_partners](https://docs.typo3.org/p/fgtclb/academic-partners/main/en-us/Developers/Index.html),
[academic_programs](https://docs.typo3.org/p/fgtclb/academic-programs/main/en-us/Developers/Index.html)
and
[academic_projects](https://docs.typo3.org/p/fgtclb/academic-projects/main/en-us/Developers/Index.html)
describe their events in detail, with examples.

| Event | Dispatched | A listener may |
| --- | --- | --- |
| `\FGTCLB\AcademicBase\Event\ModifyTcaSelectFieldItemsEvent` | when the backend builds the items of a select field of **academic_jobs** (type, employment type), **academic_persons** (the contract and the shown fields of the plugins) or **academic_contacts4pages** (the contract and the addresses of a contact), and when the job form of **academic_jobs** builds its type and employment type selects in the frontend | replace the items and the other item provider parameters |
| `\FGTCLB\AcademicBase\Event\ModifyPluginViewEvent` | in every plugin of **academic_bite_jobs**, **academic_contacts4pages**, **academic_jobs**, **academic_partners**, **academic_persons**, **academic_programs** and **academic_projects**, once each time an action renders its view, after the action assigned its own variables | assign further view variables, see [Adding a variable to the view of a plugin](https://docs.typo3.org/permalink/fgtclb/academic-base:developers-extension-points-plugin-view@main) |
| `\FGTCLB\AcademicBase\Event\CollectFrontendIconsEvent` | once each time the frontend icon registry is built, when the system caches are built or warmed up, not per request | contribute icons from code, see [Frontend icons](https://docs.typo3.org/permalink/fgtclb/academic-base:configuration-frontend-icons@main). An entry of a `Configuration/FrontendIcons.php` with the same identifier replaces a contributed icon |
| `\FGTCLB\AcademicBiteJobs\Event\ModifyBiteJobPostingsRequestEvent` | in the job list of **academic_bite_jobs**, after the request to the B-ITE API is built from the plugin settings and before it is sent | replace the payload of the request |
| `\FGTCLB\AcademicBiteJobs\Event\ModifyBiteJobPostingsEvent` | in the job list of **academic_bite_jobs**, after the response of the B-ITE API is decoded, also after a failed request, and before the limit is applied | replace the postings |
| `\FGTCLB\AcademicContacts4pages\Event\ModifyPageContactsEvent` | in the contacts content element and in the data processor of the page contacts, after the contacts of the page are read and before they are grouped by role | replace the contacts |
| `\FGTCLB\AcademicJobs\Event\AfterSaveJobEvent` | in the job form plugin, after a submitted job is saved | change the page redirected to, and how the confirmation message is shown |
| `\FGTCLB\AcademicPartners\Event\ModifyPartnerDemandEvent` | in the partner list and the partner map, before the partners are queried | replace the demand |
| `\FGTCLB\AcademicPartners\Event\ModifyPartnerListEvent` | in the partner list and the partner map, after the query | replace the partners and the categories, assign further view variables |
| `\FGTCLB\AcademicPrograms\Event\ModifyProgramDemandEvent` | in the program list and the program finder, before the programs are queried | replace the demand |
| `\FGTCLB\AcademicPrograms\Event\ModifyProgramListEvent` | in the program list and the program finder, after the query | replace the programs and the categories, assign further view variables |
| `\FGTCLB\AcademicPrograms\Event\ModifyProgramDataEvent` | on a program page, after its data is built from the page record and before the facts are built from it | replace the data the page template receives |
| `\FGTCLB\AcademicProjects\Event\ModifyProjectDemandEvent` | in the project list, before the projects are queried | replace the demand |
| `\FGTCLB\AcademicProjects\Event\ModifyProjectListEvent` | in the project list, after the query | replace the projects and the categories, assign further view variables |
| `\FGTCLB\AcademicPersons\Event\ModifyProfileDemandEvent` | in the list, list-and-detail and card plugins, before the profile query and the query of the letter navigation are built from the demand | change or replace the demand |
| `\FGTCLB\AcademicPersons\Event\ModifyProfileQueryEvent` | right before the profile query of the list, list-and-detail and card plugins, of the letter navigation and of the selected-profiles plugin is executed | add conditions |
| `\FGTCLB\AcademicPersons\Event\ModifyContractQueryEvent` | right before the contract query of the selected-contracts plugin is executed | add conditions |
| `\FGTCLB\AcademicPersons\Event\ModifyProfileTitlePlaceholderReplacementEvent` | when the page title of a profile's detail view is built, once per placeholder of the title format | replace the value of the placeholder |
| `\FGTCLB\AcademicPersons\Event\ModifyProfileImageMetadataEvent` | right before the metadata of a profile image is written, for the file and for the file reference | change or empty the fields that are written |
| `\FGTCLB\AcademicPersons\Event\AfterProfileUpdateEvent` | after a profile was created or updated: by a backend save or an import through the DataHandler, by the commands `academic:createprofiles` and `academic:updateprofiles`, and by the profile editing of **academic_persons_edit** | react to it; the profile, its site and the origin of the update are read only |
| `\FGTCLB\AcademicPersons\Event\BeforeImportedRecordWriteEvent` | in the import writer of **academic_persons**, once for every record of a person it is about to write | change the values that are written, veto the record |
| `\FGTCLB\AcademicPersons\Event\ChooseProfileFactoryEvent` | in the commands `academic:createprofiles` and `academic:updateprofiles`, once per frontend user | choose the profile factory that creates or updates the profile |
| `\FGTCLB\AcademicPersons\Event\BeforeProfileMappedFromFrontendUserEvent` | in the commands `academic:createprofiles` and `academic:updateprofiles`, before the data of a frontend user is mapped: once per frontend user on creation, once per synchronised profile on update (not for a profile whose `skip_sync` flag is set) | add or change values of the data, skip the frontend user on creation or the profile on update |
| `\FGTCLB\AcademicPersons\Event\AfterProfileMappedFromFrontendUserEvent` | in the same commands, after the data of a frontend user was mapped and before the profile is saved | change the profile |
| `\FGTCLB\AcademicPersonsEdit\Event\BeforeProfileEditingWriteEvent` | in the profile editing of **academic_persons_edit**, once for every write the editor accepted, before anything of it is stored: the profile, its switches and image, and its documents and contacts | refuse the write with a reason the person is shown, or replace the values it stores, which are validated again |

One event is not on this list. The event the commands
`academic:createprofiles` and `academic:updateprofiles` dispatch
before they set up the environment of a frontend user's site,
`ModifyProfileCommandEnvironmentStateBuildContextForFrontendUserEvent`,
is marked `@internal` as experimental, like the environment handling it
belongs to. The event the 2.4 changelog of **academic_persons_edit**
mentions for filling form data from other sources before it is written is
`BeforeProfileEditingWriteEvent` from 3.0 on.

### Adding a variable to the view of a plugin {#developers-extension-points-plugin-view}

One listener of `ModifyPluginViewEvent` serves every plugin. The context
it hands over names the plugin and the action, so a listener that means one of
them checks both. The plugin name is the one the plugin is registered with,
`List`, `Detail` or `ProjectListSingle` for example, and the
extension name tells two plugins of the same name apart:

**EXT:my_sitepackage/Classes/EventListener/AddOfficeHoursLink.php**

```php
namespace MyVendor\MySitepackage\EventListener;

use FGTCLB\AcademicBase\Event\ModifyPluginViewEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;

final class AddOfficeHoursLink
{
    #[AsEventListener]
    public function __invoke(ModifyPluginViewEvent $event): void
    {
        $context = $event->getPluginControllerActionContext();
        if ($context->getControllerExtensionName() !== 'AcademicPersons'
            || $context->getActionName() !== 'detail'
        ) {
            return;
        }
        $event->getView()->assign('officeHoursPageId', 42);
    }
}
```

The variable is then available to the templates, partials and sections of
that plugin, and a template override renders it.

The event runs after the action assigned its own variables, so a listener
that assigns one of them again replaces it in the view, and only there: the
pagination and the page title keep the profiles the query returned, for
example. A change of which records are shown belongs in a demand or a query
event. One variable is assigned after the event on purpose and cannot be
replaced at all: the validations of the job form.

## What an event hands over {#developers-extension-points-types}

A listener types against whatever an event's methods declare, so these types
are public API together with the events. Classes of TYPO3 and of other
packages are not listed; their own documentation applies.

| Type | Handed over by |
| --- | --- |
| `\FGTCLB\AcademicBase\Domain\Model\Dto\PluginControllerActionContextInterface` | every event a plugin action dispatches: the request, the site and its language, the content object, the settings of the content element and the plugin name. Every getter that can lack its value returns `null`, without an error. The application type is the exception: for a request without a type it can resolve, TYPO3 raises its own exception, which only a request built by hand can cause. |
| `\FGTCLB\AcademicPersons\Domain\Model\Dto\PluginControllerActionContextInterface` | the title placeholder event of **academic_persons**, which still declares this copy of the interface above. It adds nothing to it, is deprecated, and is removed in 4.0; type a listener against the **academic_base** interface. |
| `\FGTCLB\AcademicPersons\Domain\Model\Dto\DemandInterface` | the profile demand and query events |
| `\FGTCLB\AcademicPersons\Domain\Model\Dto\ProfileDemand` | the profile demand and query events as the demand of the list, list-and-detail and card plugins, which they declare as the interface above |
| `\FGTCLB\AcademicPartners\Domain\Model\Dto\PartnerDemand` | the partner demand and list events |
| `\FGTCLB\AcademicPrograms\Domain\Model\Dto\ProgramDemand` | the program demand and list events |
| `\FGTCLB\AcademicPrograms\Domain\Model\ProgramData` | the program data event |
| `\FGTCLB\AcademicProjects\Domain\Model\Dto\ProjectDemand` | the project demand and list events |
| `\FGTCLB\CategoryTypes\Collection\CategoryCollection` | the partner, program and project list events |
| `\FGTCLB\CategoryTypes\Collection\FilterCollection` | the partner, program and project demands, which carry the category filter of the request |
| `\FGTCLB\AcademicPersons\Profile\ProfileFactoryInterface` | the profile factory event, which takes an implementation of it |
| `\FGTCLB\AcademicPersons\Event\ProfileUpdateOrigin` | the profile update event |
| `\FGTCLB\AcademicPersons\Profile\ProfileActionType` | the profile factory event and the two events of the frontend user synchronisation |
| `\FGTCLB\AcademicBase\Form\FlashMessageCreationMode` | the job save event (its old name in **EXT:academic_jobs**, `\SaveForm\FlashMessageCreationMode`, is a deprecated alias) |
| `\FGTCLB\AcademicPersonsEdit\Event\ProfileEditingAction` | the write event of the profile editing |
| `\FGTCLB\AcademicContacts4pages\Event\PageContactsOutput` | the page contacts event |

## Interfaces {#developers-extension-points-interfaces}

A project implements an interface, types against it, or aliases it to an
implementation of its own where the container hands one out.

| Interface | What it is for |
| --- | --- |
| `\FGTCLB\CategoryTypes\Collection\GetCategoryCollectionInterface` | A model that carries categories of **category_types** implements it, as the partner, program and project models do. |
| `\FGTCLB\AcademicPersonsEdit\Service\ProfileRichTextSanitizerInterface` | The sanitizer the profile editing runs rich text through. A project replaces it by aliasing this interface to its own implementation, in an extension that depends on **academic_persons_edit**. |
| `\FGTCLB\AcademicPersons\Types\TypesInterface` | What the lists of address, email and phone number types are read through. The lists come from the extension configuration, and the extension asks for them by their own class names, so a project changes them there; an implementation of its own is not used. |
| `\FGTCLB\AcademicPersons\DemandValues\DemandValuesInterface` | The same for the sorting and grouping values the profile list offers. |
| `\FGTCLB\AcademicPrograms\Domain\Model\ProgramFactsSourceInterface` | What the facts of a program are built from: the program model of the content elements and the data of the program page implement it. |

The abstract classes that implement some of them for the extensions
themselves are not public API.

## Services and classes a project names {#developers-extension-points-services}

These are named in a project's configuration, or injected into its code. Use
them as they are; they are not meant to be subclassed or replaced.

| Class | Named |
| --- | --- |
| `\FGTCLB\AcademicBase\Imaging\IconProvider\CurrentColorSvgIconProvider` | as the `provider` of an icon in `Configuration/Icons.php` or `Configuration/FrontendIcons.php`, for an SVG drawn in `currentColor` |
| `\FGTCLB\AcademicContacts4pages\DataProcessing\ContactsProcessor` | as a data processor of a page template, for the contacts of the page, by its identifier `academic-page-contacts` or by class name |
| `\FGTCLB\CategoryTypes\Backend\FormEngine\CategoryTypeItemsProcFunc` | as the `itemsProcFunc` of a select field of a project's TCA or FlexForm that offers category types |
| `\FGTCLB\CategoryTypes\Registry\CategoryTypeRegistry` | injected, to read the registered category types |
| `\FGTCLB\CategoryTypes\Backend\PageCategorySummaryRenderer` | injected into a listener that shows the category summary of a page type of its own in the page module |
| `\FGTCLB\AcademicPersons\Profile\FrontendUserProfileMapper` | injected into a profile factory of its own, to apply the configured map of frontend user fields |
| `\FGTCLB\AcademicPersons\DataHandling\ProfileWriteCorrelation` | in import code, as `ProfileWriteCorrelation::Import`, to mark a DataHandler run that writes profiles, so the profile update event announces it with the origin `ProfileUpdateOrigin::Import`; and as `ProfileWriteCorrelation::Internal` in a listener of the profile update event that writes profiles through the DataHandler, so that write is not announced again |
| `\FGTCLB\AcademicPersons\Import\ImportedRecordFinder` | injected into import code, to find the person record that carries an import identifier, so the next run updates it |
| `\FGTCLB\AcademicPersons\Import\ProfileImportWriter` | injected into import code, to create and update the persons of a source and to retire the ones it no longer supplies |
| `\FGTCLB\AcademicPersons\Import\ImportedProfile`, `\FGTCLB\AcademicPersons\Import\ImportedContract`, `\FGTCLB\AcademicPersons\Import\ImportedContact` | created by import code, one person each, and handed to the import writer |
| `\FGTCLB\AcademicPersons\Import\RetirePolicy` | handed to the import writer, to hide or to delete what a source no longer supplies |
| `\FGTCLB\AcademicPersons\Import\ImportResult`, `\FGTCLB\AcademicPersons\Import\ImportedRecordResult`, `\FGTCLB\AcademicPersons\Import\ImportedRecordOutcome` | returned by the import writer, what happened to each record |

## Base classes {#developers-extension-points-base-classes}

| Class | What it is for |
| --- | --- |
| `\FGTCLB\AcademicPersons\Profile\AbstractProfileFactory` | The base of a profile factory a project chooses with the profile factory event. It dispatches the two events of the frontend user synchronisation. A subclass implements the protected methods `createProfileFromFrontendUser()`, which may return `null` to create nothing, and `updateProfileFromFrontendUser()`. |

## Traits {#developers-extension-points-traits}

| Trait | What it is for |
| --- | --- |
| `\FGTCLB\AcademicBase\Controller\GetCurrentContentRecordMethodTrait` | Returns the record of the current content element, which a plugin assigns as the `record` view variable so that the header of the content element renders on TYPO3 v14. |
| `\FGTCLB\AcademicBase\Controller\GetSelectItemsForTcaManagedTableFieldMethodTrait` | Reads the items of a select field as the backend would offer them, with the item provider applied and the labels translated. |

## Domain models {#developers-extension-points-models}

The domain models are what the templates read, so their public getters are
public API. A project may extend a model to add fields of its own. Extbase
creates a model through `GeneralUtility::getClassName()`, so the way to
do that is a subclass registered as the XCLASS of the model:

**EXT:my_extension/ext_localconf.php**

```php
$GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][\FGTCLB\AcademicPersons\Domain\Model\Profile::class] = [
    'className' => \MyVendor\MyExtension\Domain\Model\Profile::class,
];
```

Extbase maps the subclass by its own class name, so it needs an entry in the
project's [`Configuration/Extbase/Persistence/Classes.php`](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/FileStructure/Configuration/Extbase/Persistence/Index.html#file-extension-configuration-extbase-persistence-classes-php): the
`tableName` of the model, and its `recordType` if the extension
declares one - a subclass inherits the `properties` of the parent entry,
but neither of those two. The new columns need their TCA and their database
fields; a `properties` entry is needed only for a column whose name does
not match the property. A model the extensions create themselves with
`new`, a profile a profile factory creates for instance, is still of the
original class. The [upgrade check](https://docs.typo3.org/permalink/fgtclb/academic-base:upgrade-check-configuration@main) lists such an XCLASS as a notice: it does not
fail the check, and it is a reminder to make sure the getters and setters the
subclass overrides still exist after an update.

| Extension | Models |
| --- | --- |
| **academic_contacts4pages** | `\FGTCLB\AcademicContacts4pages\Domain\Model\Contact`, `\FGTCLB\AcademicContacts4pages\Domain\Model\Role` |
| **academic_jobs** | `\FGTCLB\AcademicJobs\Domain\Model\Job` |
| **academic_partners** | `\FGTCLB\AcademicPartners\Domain\Model\Partner`, `\FGTCLB\AcademicPartners\Domain\Model\Partnership`, `\FGTCLB\AcademicPartners\Domain\Model\Role` |
| **academic_persons** | `\FGTCLB\AcademicPersons\Domain\Model\Address`, `\FGTCLB\AcademicPersons\Domain\Model\Contract`, `\FGTCLB\AcademicPersons\Domain\Model\Email`, `\FGTCLB\AcademicPersons\Domain\Model\FrontendUser`, `\FGTCLB\AcademicPersons\Domain\Model\FunctionType`, `\FGTCLB\AcademicPersons\Domain\Model\Location`, `\FGTCLB\AcademicPersons\Domain\Model\OrganisationalUnit`, `\FGTCLB\AcademicPersons\Domain\Model\PhoneNumber`, `\FGTCLB\AcademicPersons\Domain\Model\Profile`, `\FGTCLB\AcademicPersons\Domain\Model\ProfileInformation` |
| **academic_persons_sync** | `\FGTCLB\AcademicPersonsSync\Domain\Model\FrontendUser` |
| **academic_programs** | `\FGTCLB\AcademicPrograms\Domain\Model\Program` |
| **academic_projects** | `\FGTCLB\AcademicProjects\Domain\Model\Project` |
| **category_types** | `\FGTCLB\CategoryTypes\Domain\Model\Category`, `\FGTCLB\CategoryTypes\Domain\Model\CategoryType` |

The models of **category_types** are not Extbase models; the extension
builds them itself. Their getters are public API all the same, but they cannot
be extended through an XCLASS.

## The extension points every extension should have {#developers-extension-points-minimum}

Three kinds of extension point together make subclassing and XCLASSing
unnecessary. Not every extension offers all three yet:

| Extension point | Offered today | Planned |
| --- | --- | --- |
| A view event per plugin action, to assign further view variables | the plugins of the seven extensions named at the event above, through one event | none: the profile editing of **academic_persons_edit** has an event before each of its writes instead |
| A demand event per repository query a plugin runs, to change what is queried | **academic_persons**, **academic_partners**, **academic_programs**, **academic_projects** | the list plugins of the other extensions |
| An after-save event per write, to react to a saved record | the profile of **academic_persons**, the job form of **academic_jobs** | the other writes of the frontend editing |

A planned extension point is listed here once it is dispatched, and not
before.
