---
title: "Validation settings"
manual: "Academic Profiles"
version: "main"
source: "Configuration/Validations/Index.rst"
rendered: "2026-10-02T20:47:49+00:00"
---

# Validation settings {#configuration-validations}

Every field of `Configuration/AcademicPersons/Settings.yaml` carries a
list of **flags** that say whether it is required, read only or disabled, and
what kind of value it takes. One list drives **both editing contexts**:

-   the TYPO3 backend record editor (FormEngine), through generated TCA, and
-   the editing frontend of [EXT:academic_persons_edit](https://extensions.typo3.org/extension/academic_persons_edit).

This is why the file ships with **academic_persons**, which owns the
records and their TCA, and not with the editing extension. The backend half
applies even when the editing extension is not installed. Where the fields
live - the `profile`, `special`, `contracts` and
`documentSections` maps - is documented on the
[Profile sections](../Sections/Index.html#configuration-sections) page; this page is about the
flags.

> [!WARNING]
> **Attention**
>
> The syntax of this file is still considered experimental and may change in
> a future release.

## Where the flags are declared {#configuration-validations-where}

A profile, contract or contact field carries its flags as a list:

```yaml
profile:
  gender:
    section: information
    fieldType: select
    renderType: select
    validators:
      - required
  firstName:
    section: information
    fieldType: input
    renderType: text
    validators:
      - readonly
      - disabled
```

A document section carries a map from field to flag list, with an expanded map
for a rich text field:

```yaml
documentSections:
  publications:
    validators:
      title:
        - required
      year:
        - required
        - number
      link:
        - url
      description:
        editor:
          type: ckeditor
          limit: 500
```

A field without flags is unconfigured: it is editable, not required, and no
validator runs for it. A field that is not listed at all is not offered by the
editing frontend.

## Available flags {#configuration-validations-flags}

Flag names are matched case insensitively. Anything not listed here is kept in
the list and has no effect.

| Flag | Effect |
| --- | --- |
| `required` | The field must not be empty. Adds a *not empty* validation in the frontend and marks the field required in the backend. |
| `disabled` | The field must not be edited at all. See the note below. |
| `readonly` | The field is shown but cannot be written. |
| `frontendreadonly` | The field is shown but cannot be written in the editing frontend. The backend record editor keeps it editable. See the note below. |
| `email` | The value must be a valid email address; the field is rendered as an email input and the TCA column becomes an `email` column. |
| `url` | The value must be a valid URL; the field is rendered as a URL input. The TCA column is untouched. |
| `number` | The field is rendered as a number input and the TCA column becomes a `number` column. No additional server side validation is performed. |
| `date` | The field is rendered as a date input. The TCA column keeps its own `datetime` configuration. |
| `tel` | The field is rendered as a telephone input. No phone number format is enforced, and the TCA column is untouched. |
| `textarea` | The field is rendered as a multi line text control. The TCA column is untouched. |
| `html` | The field is rich text: the editing frontend renders the rich text editor and sanitises the submitted markup. The TCA column is untouched. |

Only `required`, `email` and `url` run a validator on the
server; the other flags select the control and the input normalisation.
Validator class names and validator options cannot be put in the list.

> [!NOTE]
> `disabled` and `readonly` both **cancel** `required`. A
> field that cannot be edited cannot be demanded from the editor, so combining
> them has no effect - the field is simply locked.
>
> `disabled` additionally implies `readonly`. FormEngine has no
> equivalent of the HTML `disabled` attribute, so a disabled field is
> presented as read only in the backend.
>
> `frontendreadonly` locks the field for profile owners only. It
> cancels `required` in the editing frontend, because an owner cannot
> correct a value they cannot edit, while the backend record editor still
> requires the field. Listed together with `readonly` or
> `disabled`, the field is locked in the backend as well.

## Character limits {#configuration-validations-limits}

A rich text field may limit the number of **readable** characters - markup is
not counted. A profile or contract field declares it next to its render type,
a document field in its editor block:

```yaml
profile:
  miscellaneous:
    section: aboutme
    fieldType: textarea
    renderType: ckeditor
    characterLimit: 1000
    validators:
      - html

documentSections:
  publications:
    validators:
      description:
        editor:
          type: ckeditor
          limit: 500
```

The limit is effective only on a `ckeditor` control; on any other control
it is ignored. It is checked on the server and shown by the editing frontend;
it is **never copied into the TCA**, because FormEngine's `max` would count
the markup.

## Fields that are locked by default {#configuration-validations-defaults}

The three name fields ship as `readonly` and `disabled`:

```yaml
profile:
  firstName:
    validators:
      - readonly
      - disabled
  middleName:
    validators:
      - readonly
      - disabled
  lastName:
    validators:
      - readonly
      - disabled
```

This is intentional. Profile names are usually owned by the connected frontend
user record - commonly fed from a directory service such as LDAP or Active
Directory, and synchronised into the profile - so they must not be overwritten
from an editing form.

The consequences, which surprise people who did not expect them:

-   **First name**, **Middle name** and **Last name**
    are **read only in the backend** record editor, for every backend user.
-   The same three fields are rendered locked in the editing frontend, and a
    value submitted for them is discarded on the server.

If the profile names are maintained in TYPO3 rather than synchronised from
elsewhere, remove the two flags as described below.
Where some profiles are synchronised and others are created by editors, lock
the names on the synchronised ones only, with
[managed fields](../ManagedFields/Index.html#configuration-managed-fields-example).

## Effects in the TYPO3 backend {#configuration-validations-backend}

The flags of every section are merged into the TCA of the matching table, so a
field locked with `readonly` or `disabled` is read only in the
record editor and a required field is marked as such. `frontendreadonly`
is the one lock that does not reach the TCA. The sections map to these tables:

| Section | Table |
| --- | --- |
| `profile` fields and `special.skipSync` | `tx_academicpersons_domain_model_profile` |
| `contracts.fields` | `tx_academicpersons_domain_model_contract` |
| `contracts.contactSections.emailAddresses` | `tx_academicpersons_domain_model_email` |
| `contracts.contactSections.phoneNumbers` | `tx_academicpersons_domain_model_phone_number` |
| `contracts.contactSections.physicalAddresses` | `tx_academicpersons_domain_model_address` |
| every other `documentSections` entry | `tx_academicpersons_domain_model_profile_information`, as `columnsOverrides` of the record type of that section |

`special.hidden` is the one exception. Its flags decide whether owners may
show or hide their profile in the profile editor, and an installation sets them
when editors are meant to decide instead, so they never make the backend
checkbox **Visible** read only.

The property name is translated to the database column automatically:
`firstName` addresses `first_name`; a field that names a
`fieldName` addresses that column instead.

The seven timeline sections share one table, so their flags apply to **their
record type only**: a required title of publications does not make the title
of a lecture required. The `fieldType` and `renderType` of a field
never reach the TCA - the column keeps the type its TCA file declares.

The flags are applied once the TCA is compiled, after every
`Configuration/TCA/Overrides` file. A site package that replaces one of
these columns keeps what the settings say about it, and a `required` or
`readOnly` its TCA override sets on a configured column is replaced by the
settings. State a lock in the settings instead. A value that has to differ in
the backend only is set by a listener of
`\TYPO3\CMS\Core\Configuration\Event\AfterTcaCompilationEvent` ordered
after `academic-persons/apply-settings-to-tca`, see
[Breaking: Settings apply after the TCA overrides](../../Changelog/3.0/Breaking-SettingsApplyAfterTcaOverrides.html#breaking-settings-apply-after-tca-overrides).

A project field, declared with `custom: true`, gets its flags only when
its column may be used: the column is in the profile TCA, it is no system column
and no column of the shipped profile model, its type is `input`, `text`,
`email`, `link`, `number` or `check`, and the renderer and validators of
the field fit that type. Any other column gets nothing and raises a deprecation
notice naming the field, the column and the reason. The backend and the install
tool keep working. A regular field whose column the TCA does not have is left
out without a notice.

## Effects in the editing frontend {#configuration-validations-frontend}

When [EXT:academic_persons_edit](https://extensions.typo3.org/extension/academic_persons_edit) is installed,
the same flags are used three times:

1.  The control is rendered with the matching `disabled`,
    `readonly` and `required` attributes and the input type the
    flags select.
1.  `required`, `email` and `url` add server side validation
    of the submitted data, and a character limit is enforced.
1.  A `disabled`, `readonly` or `frontendreadonly` property
    is **never written** to the record, whatever the request contains. This is
    deliberate: it protects already stored data, and it is what prevents a
    locked field from being emptied when a form is submitted. A submitted value
    for it is ignored and the other values of the request are stored, so a
    contract or contact with a locked field is saved as usual. The same holds
    for a field the synchronisation owns on the record, see
    [In the profile editor](../ManagedFields/Index.html#configuration-managed-fields-editor).

> [!NOTE]
> A record an owner creates in the editing frontend, such as a new contract,
> is stored without a value for a field locked with `frontendreadonly`,
> even when the backend requires it. The backend record editor asks for it
> the next time the record is saved there.

Validation never falls back from one section to another: a contact record is
validated against its contact section, a timeline entry against the section of
its record type, and the profile against its profile sections.

## Overriding the flags {#configuration-validations-override}

The flags live in the map that carries the field, so changing them means
overriding that map - see [Overriding the file](../Sections/Index.html#configuration-sections-override). The files are merged recursively, so an
override names the field and its `validators` list and nothing else. The
list itself is replaced as a whole, which is how a flag is dropped.

Example - making the profile names editable again, in the backend and in the
editing frontend. The three name fields ship with `readonly` and
`disabled`; an empty list clears them, and every other key of those
fields, of the other fields and of the layout stays as shipped:

```yaml
profile:
  firstName:
    validators: []
  middleName:
    validators: []
  lastName:
    validators: []
```

A flag is added the same way, by restating the list with it:

```yaml
profile:
  title:
    validators:
      - required
```

Example - keeping the synchronised profile names away from their owners while
backend editors can still correct them. `frontendreadonly` replaces both
shipped flags, and `required` keeps the first and last name required in
the backend record editor, as the TCA of the profile table declares them:

```yaml
profile:
  firstName:
    validators:
      - required
      - frontendreadonly
  middleName:
    validators:
      - frontendreadonly
  lastName:
    validators:
      - required
      - frontendreadonly
```

> [!NOTE]
> Because both editing contexts read the same configuration, an override
> changes them together, with `frontendreadonly` as the one exception.
> Unlocking the profile names for the editing frontend also makes those
> columns writable in the backend record editor.

There is no TypoScript and no site set equivalent for these settings.

## Migrating a pre-3.0 override {#configuration-validations-migration}

Before 3.0 the file had two top-level maps, `validations` with one flag
list per record type and `profileInformationsTypes` with the seven
timeline entry types, and the manual told integrators to restate the complete
`validations` block in the site package. Such a file keeps working after
the update: the two keys are mapped onto the section maps at runtime, before
the settings graph is built, and a warning naming the package and the key is
logged once per cache build. The mapping is transitional and is removed in
academic_persons 4.0, so the override should be rewritten.

The console command prints the replacement:

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

For every active package that still ships a legacy key it prints the package,
the keys it found, one comment line per entry that could not be mapped, and
the four maps `profile`, `special`, `contracts` and
`documentSections` as the runtime mapping produces them - the complete
document that replaces the legacy keys in that package's file. It exits with
1 when such a package exists and with 0 otherwise, so a deployment pipeline
can run it as a check. The command never writes the file: the override lives
in a site package that is under version control and usually deployed read
only, so the printed maps are pasted into the file after review, and the
TYPO3 caches are flushed afterwards. When EXT:reports is installed, the
**Status** report lists the same packages under
**Academic Persons** as a warning.

The printed maps restate everything the runtime produces. Once they replace
the legacy keys, `vendor/bin/typo3 academic:persons:settings:migrate --delta`
reduces the file to the entries that differ from what the packages loaded
before it configure - see
[Overriding the file](../Sections/Index.html#configuration-sections-override).

How the legacy keys map:

| Legacy key | Mapped onto |
| --- | --- |
| `validations.profile.<property>` | `profile.<field>.validators` |
| `validations.contract.<property>` | `contracts.fields.<field>.validators` |
| `validations.emailAddress.{email, type}` | `contracts.contactSections.emailAddresses.fields.{emailAddress, emailAddressType}.validators` |
| `validations.phoneNumber.{phoneNumber, type}` | `contracts.contactSections.phoneNumbers.fields.{phoneNumber, phoneNumberType}.validators` |
| `validations.physicalAddress.<property>` | `contracts.contactSections.physicalAddresses.fields.<field>.validators`, `type` onto `physicalAddressType` |
| `validations.profileInformation.<property>` | `documentSections.<section>.validators.<field>` of every timeline section; `yearStart`, `yearEnd` and `bodytext` onto `from`, `to` and `description` |
| `profileInformationsTypes.<section>` | the `label` of `documentSections.<section>`; its `type` and `fieldName` are reported, not applied |

A field is matched by its key or by the property it names, so
`emailAddress.email` reaches the `emailAddress` field whose
`propertyName` is `email`. A legacy set decides the six flags the
old shape knew - `required`, `readonly`, `frontendreadonly`
(added in 2.4), `disabled`, `email` and `number` \- for
**every** field of its target: a field
the set does not list has none of them, exactly as an unlisted property was
unconfigured before, which is what made the 2.x example above unlock the
profile names by not listing them. The flags the old shape could not express
\- `url`, `date`, `tel`, `textarea`, `html` \-
stay as the section maps declare them.

Two things are not mapped and are reported by the command and in the log:

-   A property the section maps do not know, and an eighth timeline entry
    type declared under `profileInformationsTypes`, are skipped. The
    type needs a profile relation and a TCA column the settings never
    created; the Breaking entry on the section based settings describes how
    to keep one.
-   The `type` and `fieldName` of a timeline entry type are not
    applied. Until 2.4 the two generated the inline column of the profile
    table, so overriding one moved the backend relation and the frontend
    selection together; since 3.0.0 the seven relations are declared by the
    TCA of the profile table, and applying the override would move the
    frontend half alone - records created in the editing frontend would be
    invisible in the backend, and the other way round. The section keeps the
    record type and the relation field that match the TCA, and the value the
    override named is printed as a note. Act on that note: a timeline type of
    your own needs its own column in a TCA override of the profile table, as
    [The document sections](../Sections/Index.html#configuration-sections-documents) and the [Upgrading from 2.4 to 3.0.0](../../Upgrade/Index.html#upgrade) page
    describe.
