---
title: "Upgrading from 2.4 to 3.0.0"
manual: "Academic Profiles"
version: "main"
source: "Upgrade/Index.rst"
rendered: "2026-10-02T15:38:26+00:00"
---

# Upgrading from 2.4 to 3.0.0 {#upgrade}

Version 3.0.0 changes the shape of
`Configuration/AcademicPersons/Settings.yaml`, the public detail template
and - together with [`fgtclb/academic-persons-edit`](https://packagist.org/packages/fgtclb/academic-persons-edit) \- the complete
profile editing frontend. Each of those changes carries its own changelog entry
with the detail; this page is the **order** they have to be applied in.

The order matters. One wizard reads a database column TYPO3 v14 no longer has,
and one repairs relations the new editor then writes. Work through the steps
from top to bottom, on a copy of the production database first.

## The steps at a glance {#upgrade-overview}

| Step | What it does | What happens if it is skipped |
| --- | --- | --- |
| 1.  Update the packages | Installs 3.0.0 of every academic extension of the installation. | Nothing below applies. |
| 1.  Run the plugin migration wizards, still on TYPO3 v13 | Moves the `list_type` plugin records onto their own `CType`. | The content elements stop rendering on TYPO3 v14, where the column the wizards read no longer exists. |
| 1.  Update the database schema | Applies the column changes of 3.0.0, among them the unsigned timeline year columns and the workspace columns. | The wizards of steps 4 to 6 find columns the installation does not have, and the editor writes into them too. |
| 1.  Seed the sort order of the organisational unit contracts | Fills the sort column the organisational unit relation gained, from the order the unit forms show today. | Every organisational unit form lists its contracts in whatever order the database returns until the unit is saved once. |
| 1.  Generate the URL segments of the filter records | Gives every function type and organisational unit the slug the filter routes of the persons list read. | A list filtered by such a record keeps its filter as a query argument instead of a speaking URL segment. |
| 1.  Repair the profile image relations | Reduces duplicate references, corrects relation counters and marks the translations that carry an image of their own. | A translation loses its own image at the next synchronisation, and duplicate references keep rendering the wrong file. |
| 1.  Migrate the settings override | Replaces the pre-3.0 keys of a site package with the section maps. | The installation runs on the legacy overlay, which is removed in 4.0 - and a renamed `type` or `fieldName` stays silently broken. |
| 1.  Adapt templates, icons, TypoScript and repository overrides | Re-applies project overrides to the new template tree, makes the JSON page type reachable, moves repository customizations to the query events, drops a project hook that announced backend saves, completes a project's own plugin action context, and moves listeners of the removed profile view events. | The editor cannot save, an overridden detail view loses the configurable layout, a repository override is either a fatal error or silently unused, every backend save is announced and synchronised twice, a project's own plugin action context is a fatal error, and a listener of a removed event is no longer called. |

> [!NOTE]
> The TYPO3 core update itself is not a step of this page; it is an upgrade
> of its own with its own manual. Where it belongs matters for exactly one
> step: the plugin migration wizards of step 2 read a column TYPO3 v14 no
> longer declares, so the core update comes **after** step 2. Every other
> step works the same on TYPO3 v13 and on v14.

## 1\. Update the packages {#upgrade-step-packages}

The academic extensions are released together and depend on each other, so they
are updated in one go:

```bash
composer require 'fgtclb/academic-persons':'^3' 'fgtclb/academic-persons-edit':'^3'
```

In a classic (non-Composer) installation, update every academic extension in
the Extension Manager and activate **rte_ckeditor**, which
**academic_persons_edit** requires for its rich text fields since
3.0.0.

Flush all caches afterwards. The settings graph is cached in the core cache and
the cache identifier changed with the file format, so a stale entry is not read
back - but the TypoScript and TCA caches are.

## 2\. Run the plugin migration wizards while still on TYPO3 v13 {#upgrade-step-plugins}

`academicPersons_MigrateListTypeToCTypeContentElements` and
`academicPersonsEdit_pluginContent` move the content elements of both
extensions from `CType = list` plus `list_type` onto their own `CType`.
Both read `tt_content.list_type`, and **TYPO3 v14 removed that column**.

```bash
vendor/bin/typo3 upgrade:run academicPersons_MigrateListTypeToCTypeContentElements
vendor/bin/typo3 upgrade:run academicPersonsEdit_pluginContent
```

Run them on TYPO3 v13, before the core update. TYPO3 v14 removed
`tt_content.list_type` from the TCA, so the database analyzer of a v14
installation offers the column for removal and a fresh v14 installation never
has it. Both wizards ask the live schema: once the column is gone their content
element half finds nothing to do, and the records they would have migrated keep
a content type nothing renders. The `academicPersons_` wizard also migrates
the `explicit_allowdeny` values of the backend user groups, and that half
stays available on both versions.

> [!WARNING]
> `academicPersons_MigrateListTypeToCTypeContentElements` is **not
> repeatable**: a wizard that reports "nothing to do" is recorded as done and
> disappears from the upgrade module. That is why this step names both
> wizards instead of running a bare `vendor/bin/typo3 upgrade:run` \- a
> bare run on TYPO3 v14, with the column already removed, marks the wizard as
> done although it migrated nothing. The flag is cleared with
>
> ```bash
> vendor/bin/typo3 upgrade:mark:undone academicPersons_MigrateListTypeToCTypeContentElements
> ```
>
> but that only helps while `tt_content.list_type` still holds the
> values. Once the column is dropped, nothing records which plugin such a
> content element was, and the records have to be repaired by hand.

`academicPersonsEdit_removeProfileSwitcherContent` deletes the content
elements of the removed profile switcher plugin and handles both shapes, so it
can be run on either core version.

## 3\. Update the database schema {#upgrade-step-schema}

```bash
vendor/bin/typo3 extension:setup
```

In the backend the same thing is **Admin Tools > Maintenance > Analyze
Database Structure**. It applies every column change of 3.0.0. Nothing is
converted here and no value is rewritten: the timeline keeps its
`year`, `year_start` and `year_end` columns, which only turn
unsigned because the corrected TCA declares a lower bound of `0`.

> [!NOTE]
> The analyzer reports a column that left `ext_tables.sql` as *unused*
> and never drops it on its own. Accepting such an offer is a decision of the
> installation, not a step of this upgrade.

The contract column `publish` is such a column. Only a project whose own
code gave it a meaning, and left unpublished contracts out, registers and runs
`academicPersons_migrateContractPublishToHidden` now, before it lets the
analyzer drop the column. The wizard is not registered by default, because on
every other installation it would hide all contracts. See
[Important: The contract publish wizard has to be registered by hand](../Changelog/3.0/Important-ContractPublishToHiddenWizard.html#important-contract-publish-to-hidden-wizard).

## 4\. Seed the sort order of the organisational unit contracts {#upgrade-step-inline-sorting}

```bash
vendor/bin/typo3 upgrade:run academicPersons_seedContractOrganisationalUnitSorting
```

A contract is an inline child of its profile and of its organisational unit, and
both relations wrote the same `sorting` column until 3.0.0 - so saving an
organisational unit rearranged the contracts of every profile that owns one of
them. The unit relation has a column of its own from 3.0.0 on,
`organisational_unit_sorting`, which step 3 adds and this wizard fills with
the order the unit forms show today.

Run it after step 3. Without it every contract carries `0` in the new
column, so an organisational unit form lists its contracts in whatever order the
database returns until an editor saves that unit once. Nothing the frontend
renders is affected either way: profiles keep ordering their contracts by
`sorting`. See [Important: An organisational unit sorts its contracts on its own](../Changelog/3.0/Important-OrganisationalUnitSortsItsContracts.html#important-organisational-unit-sorts-its-contracts).

Contracts created after the update do not need it: they are appended to the list
of their organisational unit as they join it. Running the wizard again is
harmless all the same - it appends what has no position yet and never renumbers
what has one.

> [!NOTE]
> [`fgtclb/academic-partners`](https://packagist.org/packages/fgtclb/academic-partners) and
> [`fgtclb/academic-contacts4pages`](https://packagist.org/packages/fgtclb/academic-contacts4pages) ship the same repair for their own
> tables, as `academicPartners_seedPartnershipRoleSorting` and
> `academicContact4pages_seedContactSecondarySorting`. Run them in the same
> step where those extensions are installed.

## 5\. Generate the URL segments of the filter records {#upgrade-step-filter-slugs}

```bash
vendor/bin/typo3 upgrade:run academicPersons_fillFilterSlugs
```

Function types and organisational units gained a `slug` field in 3.0.0,
which step 3 adds. The filter routes of the persons list read it, so a list
filtered by a function type reads `/persons/function/professor` \- see
[Route enhancers](../Configuration/RouteEnhancers/Index.html#configuration-route-enhancers). The wizard gives every live record without
a slug the one a backend save would generate from its name, and leaves a slug
that is set alone.

Without it the filter form still works: a list filtered by a record without a
slug keeps its filter as a query argument. The wizard is repeatable and is
offered again whenever a record has no slug, for instance after an import of
your own wrote one without it, or after a workspace version from before the
update was published. Records the frontend user synchronisation creates get
their slug right away.

## 6\. Repair the profile image relations {#upgrade-step-images}

Only relevant where **academic_persons_edit** is installed, and only
worth running where profiles have images.

```bash
vendor/bin/typo3 upgrade:run academicPersonsEdit_repairLocalizedProfileImages
```

The profile image column is translatable from 3.0.0 on. Until then it was
excluded from localisation and the upload path wrote the relation rows by hand,
which left three shapes behind that are defects under the new model: duplicate
references on one profile, a relation counter that disagrees with the number of
references, and a translation carrying its own reference without the `custom`
localisation state - the state that keeps the next synchronisation from
replacing it with a localisation of the default-language image.

The wizard repairs all three through the TYPO3 DataHandler, so the reference
index, the record history and the localisation state are the core's. **No file
is deleted**; a file left without a relation is for the *unused files* tooling
of the Install Tool to report. Prefer the command line over the Install Tool
module for it.

Run it after step 3 and before editors start working in the new editor of a
multilingual installation. It is repeatable as well; where a 3.0.0 pre-release
recorded it as done, `vendor/bin/typo3 upgrade:mark:undone
academicPersonsEdit_repairLocalizedProfileImages` offers it again. See
[Breaking: The profile image translates](../Changelog/3.0/Breaking-ProfileImageIsTranslatable.html#breaking-profile-image-is-translatable).

## 7\. Migrate the settings override {#upgrade-step-settings}

Only relevant for an installation whose site package ships
`Configuration/AcademicPersons/Settings.yaml`.

The pre-3.0 top-level keys `validations` and
`profileInformationsTypes` are mapped onto the four section maps at
runtime, with a warning in the log, so such a file keeps working. The overlay is
transitional and is removed in academic_persons 4.0.

```bash
vendor/bin/typo3 academic:persons:settings:migrate
```

The command prints, for every active package that still ships a legacy key, the
`profile`, `special`, `contracts` and `documentSections`
maps the overlay produces - the document that replaces the legacy keys of that
package. It never writes the file, and it exits with `1` while such a package
exists, so a deployment pipeline can gate on it. Paste the printed maps into the
site package, review them, and flush all caches.

> [!WARNING]
> Read the notes the command prints. A renamed `type` or
> `fieldName` of a legacy `profileInformationsTypes` entry is
> **not applied**, and is reported instead: since 3.0.0 the seven profile
> relations and the record type each of them selects are declared by the TCA
> of the profile table, and applying such a rename would move the editing
> frontend alone, leaving the backend column and the editor writing
> different record types. The section keeps the values that match the TCA,
> so the installation is consistent - but the intention of the override is
> silently not honoured. A timeline type of your own needs its own column in
> a TCA override of the profile table, and its section under
> `documentSections` \- see [The document sections](../Configuration/Sections/Index.html#configuration-sections-documents).
> The same applies to a `type` or `fieldName` written directly
> into the new `documentSections` map: that one *is* read, and it is
> the shape that diverges from the TCA.

The printed maps state every key the overlay produced, so they carry the
result the installation runs on today. Since 3.0.0 the files are merged
**recursively**, which is a step of its own once they are in the site package:
anything the override leaves out is now inherited from
**academic_persons** rather than removed, at every level, and is
removed with `~`. Work through
[Breaking: Settings files merge recursively](../Changelog/3.0/Breaking-SettingsFilesMergeRecursively.html#breaking-settings-files-merge-recursively) before the override is
reduced to its deltas;
`vendor/bin/typo3 academic:persons:settings:migrate --delta` prints them,
and names the entries a copied map leaves out.

A site package that sets `required` or `readOnly` of a person column in
its TCA overrides loses that value to the settings since 3.0.0, which are
applied after every override. Move such a lock into the settings, see
[Breaking: Settings apply after the TCA overrides](../Changelog/3.0/Breaking-SettingsApplyAfterTcaOverrides.html#breaking-settings-apply-after-tca-overrides).

[Migrating a pre-3.0 override](../Configuration/Validations/Index.html#configuration-validations-migration) has the complete key mapping and what
is deliberately not mapped;
[Breaking: Section-based AcademicPersons settings](../Changelog/3.0/Breaking-SectionBasedAcademicPersonsSettings.html#breaking-section-based-academic-persons-settings) describes the new shape.

## 8\. Adapt templates, icons, TypoScript and repository overrides {#upgrade-step-templates}

### The public detail view {#the-public-detail-view}

`Resources/Private/Templates/Profile/Detail.html` was rewritten: it is a
dispatcher over the `profile.structure` and `profile.details`
layout, and every element is one partial below
`Resources/Private/Partials/Profile/PublicProfile/`.

**An installation that overrides the detail template keeps rendering its own
copy**, so nothing looks broken - and it loses the configurable layout
completely. The timeline properties it reads are unchanged: `{item.year}`,
`{item.yearStart}` and `{item.yearEnd}` are still there and still integers.
Two of the partials the old detail
template rendered are now only used by the list and card views, and the third,
`Partials/Profile/DataHeader.html`, is deleted - a project template that
still renders it fails at render time. See
[What an override of the detail template loses](../Configuration/Sections/Index.html#configuration-sections-detail-override) and
[Breaking: The partials of the detail view change](../Changelog/3.0/Breaking-PublicProfileDetailPartials.html#breaking-public-profile-detail-partials).

### The profile image {#the-profile-image}

The profile card and the detail view render the profile image as a
`<picture>` through the responsive image partial of
**academic_base**, and a card of a profile without an image shows a
placeholder. CSS that addressed the image directly, and a project that
replaces the partial root paths of the plugin, need an adjustment; an empty
`plugin.tx_academicpersons.image.placeholder.default` keeps those cards
without an image. See [Breaking: Profile images render as a responsive picture](../Changelog/3.0/Breaking-ProfileImagesRenderAsPicture.html#breaking-profile-images-render-as-picture).

### The profile editing view {#the-profile-editing-view}

The editing plugin of **academic_persons_edit** was replaced in place.
Every Fluid file of the removed form flow is gone, so an override of one of them
renders nothing; the new tree is `Templates/Profile/Index.html` with the
partials below `Partials/Profile/`, and the markup of the two regions the
browser builds is authored in Fluid as `<template data-pe-proto>` prototypes.
The [profile editing chapter](https://docs.typo3.org/p/fgtclb/academic-persons-edit/main/en-us/ProfileEditing/Index.html)
of that extension names every file, every hook and the four prototype
attributes an override has to keep.

### Icons {#icons}

The icon set of the editor was replaced. Five identifiers of the form flow are
gone - `academic-persons-edit-add-image`, `-add-item`, `-cancel`,
`-sort` and `-to-top` \- and the thirteen action icons of the new set are
registered in `Configuration/Icons.php` of
**academic_persons_edit**, under the identifiers listed in the [icon
table](https://docs.typo3.org/p/fgtclb/academic-persons-edit/main/en-us/ProfileEditing/Index.html#profile-editing-icons).
A template or PHP file addressing a removed identifier renders TYPO3's
`default-not-found` placeholder. The new icons are inlined as `<svg>` rather
than emitted as `<img>`, so they follow the text colour - and a site
stylesheet that selects `.t3js-icon img` no longer matches them.

### TypoScript and the JSON page type {#typoscript-and-the-json-page-type}

The write path of the editor is a `PAGE` object with
`typeNum = 1733735`, which is delivered by the site set
`fgtclb/academic-persons-edit-profile-editing` or by the static template
**Academic Persons Edit: Profile editing**. There was no such page type
in 2.4, because the old editor was a server-rendered form flow.

**Check that the site really includes one of the two.** A site package that
copied the extension's TypoScript into its own instead of including it renders
the new editor and would answer every save with the page's HTML instead of
JSON. The editor detects that case: where the request carries no such
`PAGE` object it renders a `role="alert"` message above itself
and logs the cause, naming the site set. Such a site package has to add the
`academicPersonsProfileEditingAjax` object by hand, or include the
delivered TypoScript.

A `PageType` route enhancer has to map the page type, and a web application
firewall or reverse proxy has to let it and the `X-Requested-With` header
through - see the [page type section](https://docs.typo3.org/p/fgtclb/academic-persons-edit/main/en-us/ProfileEditing/Index.html#profile-editing-page-type).

### Repository customizations {#repository-customizations}

A project that narrowed what the plugins show by **subclassing or XCLASSing**
`ProfileRepository` or `ContractRepository` has to be looked at. An
override of `findByDemand()` is a fatal error until it takes the new
trailing context parameter; an override of either `findByUids()` keeps
loading and is no longer reached by the selected-profiles and
selected-contracts plugins, silently. Both move to a listener of the query
events, which applies to every plugin and before pagination. See
[Breaking: The plugins call different repository finders](../Changelog/3.0/Breaking-ProfileAndContractFinderSignatures.html#breaking-profile-and-contract-finder-signatures) and
[Feature: Narrow the profiles and contracts a plugin shows](../Changelog/3.0/Feature-ProfileAndContractQueryEvents.html#feature-profile-and-contract-query-events).

### Project hooks announcing backend saves {#project-hooks-announcing-backend-saves}

A project that dispatched `AfterProfileUpdateEvent` from a DataHandler hook
of its own, so that backend saves synchronise the translations, removes that
hook - together with any frontend request it faked for the site. The extension
announces a backend save itself now, with the site of the profile's page, and
the project hook would announce it a second time. An import that writes
through the DataHandler can mark its run as an import instead. See
[Important: Backend saves announce profile updates](../Changelog/3.0/Important-BackendSavesAnnounceProfileUpdates.html#important-backend-saves-announce-profile-updates) and
[Feature: The profile update event carries the site and the origin](../Changelog/3.0/Feature-ProfileUpdateEventCarriesSiteAndOrigin.html#feature-profile-update-event-carries-site-and-origin).

### Classes implementing the plugin action context {#classes-implementing-the-plugin-action-context}

A class of a project that implements
`\FGTCLB\AcademicPersons\Domain\Model\Dto\PluginControllerActionContextInterface`
adds `getContentObjectRenderer()`, or is a fatal error. It keeps
implementing the persons interface while it is handed to the page title
placeholder event, which declares that type throughout 3.x, and switches
to the interface of **academic_base** with 4.0, when the persons one is
removed. See
[Breaking: The persons plugin action context extends the one of academic_base](../Changelog/3.0/Breaking-PluginControllerActionContextInterfaceExtendsBase.html#breaking-plugin-controller-action-context-interface-extends-base) and
[Deprecation: The plugin action context of academic_persons](../Changelog/3.0/Deprecation-PersonsPluginControllerActionContext.html#deprecation-persons-plugin-controller-action-context).

### Listeners of the removed profile view events {#listeners-of-the-removed-profile-view-events}

The list, detail, selected profiles and selected contracts plugins no longer
dispatch `ModifyListProfilesEvent`, `ModifyDetailProfileEvent`,
`ModifySelectedProfilesEvent` and `ModifySelectedContractsEvent`.
A listener of one of them raises no error, it is simply never called again.
Search the project code for the four class names: a listener that assigns view
variables moves to `ModifyPluginViewEvent` of **academic_base**, a
change of the demand to `ModifyProfileDemandEvent`, and a change of the
detail page title format to the setting of the content element, or to
`plugin.tx_academicpersons.settings.pageTitleFormat` for every
content element that sets none. See
[Breaking: The list, detail and selection events of the plugins are gone](../Changelog/3.0/Breaking-RemovedProfileViewEvents.html#breaking-removed-profile-view-events).

## Verifying the result {#upgrade-verify}

1.  A timeline entry of a profile shows its year in the frontend and in the
    backend record editor, and the backend form rejects a year above `9999`.
1.  The profile editing plugin loads without the "cannot be saved" alert above
    it, a field can be saved, and the browser console shows no failed request
    to the page type `1733735`.
1.  A profile image is shown in every language of a translated profile, and
    uploading a new one in one language does not change the other.
1.  `vendor/bin/typo3 academic:persons:settings:migrate` exits with
    `0`, and the log carries no legacy settings warning.
1.  A list plugin and a selected-profiles plugin show what the project's own
    code intends, where that code used to be a repository subclass or XCLASS.
1.  With `profile.allowedLanguages` of `EXT:academic_persons_edit` set, a
    last name changed in the backend reaches the translation of the profile
    after one save, and a project hook announcing backend saves is gone.
