Validation 

Configure validators per column to enforce data integrity on write operations. Validators run on every POST, PUT, and PATCH request before the record is written.

Auto-derivation from TCA 

TCA API reads your existing TCA configuration at boot time and automatically derives validators from constraints that are already declared there. No configuration is required in your resource definition — the constraints are picked up for free.

The following TCA constraints are derived automatically:

TCA column type TCA key Derived validator Notes
input, text config.max maxLength Derived when max > 0
number config.range.lower minValue Only when lower is set
number config.range.upper maxValue Only when upper is set
group, inline, file, category config.maxitems maxItems Always derived when set
group, inline, file, category config.minitems minItems Only when minitems > 0
any config.required required flag Standard TYPO3 field flag

Derivation is gap-fill only: if you declare an explicit validator of the same type in the resource config, the TCA-derived one is skipped for that type. Explicit configuration always wins.

// TCA declares max=255 for 'title', but your resource sets maxLength=20 —
// the explicit maxLength=20 is kept and 255 is NOT added.
'title' => [
    'groups'     => ['list', 'show', 'create', 'update'],
    'validators' => [
        ['type' => 'maxLength', 'max' => 20],  // wins over TCA max=255
    ],
],

// 'first_name' has no explicit validators — TCA max=255 is injected automatically.
'first_name' => [
    'groups' => ['list', 'show', 'create', 'update'],
],

// 'profile_photo' (type=file, maxitems=1 in TCA) — maxItems:1 injected automatically.
'profile_photo' => [
    'groups' => ['list', 'show', 'create', 'update'],
    'upload' => ['folder' => '1:/uploads/'],
],
Copied!

Opt-out per column 

To disable all TCA derivation for a specific column, set `'tcaValidation' => false`:

'notes' => [
    'groups'         => ['list', 'show', 'create', 'update'],
    'tcaValidation'  => false,  // no validators derived from TCA for this column
],
Copied!

The required flag follows the same gap-fill rule: it is only injected from TCA when the required key is completely absent from the column config. An explicit 'required' => false is honored and never overridden.

Validator types 

The following validator types are available, both for explicit configuration and as targets for TCA auto-derivation.

Type Parameters Error code Description
required (none) REQUIRED Field must be present and non-empty on POST/PUT. Set via the required column key, not inside validators. Skipped on PATCH for absent fields. Auto-derived from TCA config.required: true.
maxLength max (int) MAX_LENGTH Maximum string length (multi-byte safe). Auto-derived from input/text TCA config.max.
minLength min (int) MIN_LENGTH Minimum string length.
maxValue max (int|float) MAX_VALUE Maximum numeric value. Skipped for non-numeric values. Auto-derived from number TCA config.range.upper.
minValue min (int|float) MIN_VALUE Minimum numeric value. Skipped for non-numeric values. Auto-derived from number TCA config.range.lower.
maxItems max (int) MAX_ITEMS Maximum number of items in an array-valued relation field. Skipped for non-array values. Auto-derived from group/inline/file/ category TCA config.maxitems.
minItems min (int) MIN_ITEMS Minimum number of items. Skipped for non-array values. Auto-derived from config.minitems when > 0.
regex pattern (string) REGEX PCRE pattern (including delimiters) the value must match. The pattern is validated at config load time — broken patterns raise an exception immediately rather than silently failing at runtime.

Configuration 

Validators are declared in the validators array of a column definition:

'columns' => [
    'title' => [
        'groups'     => ['list', 'show', 'create', 'update'],
        'required'   => true,
        'validators' => [
            ['type' => 'maxLength', 'max' => 255],
            ['type' => 'minLength', 'min' => 3],
            ['type' => 'regex', 'pattern' => '/^[\w\s]+$/u'],
        ],
    ],
    'rating' => [
        'groups'     => ['list', 'show', 'create', 'update'],
        'validators' => [
            ['type' => 'minValue', 'min' => 1],
            ['type' => 'maxValue', 'max' => 5],
        ],
    ],
],
Copied!

Error response 

Validation failures return 422 Unprocessable Entity with a Hydra-compatible error body. All violations are collected and returned together — the request is not aborted on the first failure.

{
    "@context": "http://www.w3.org/ns/hydra/context.jsonld",
    "@type": "hydra:Error",
    "hydra:title": "Validation Failed",
    "hydra:description": "2 validation error(s)",
    "violations": [
        {
            "propertyPath": "title",
            "message": "Field 'title' is required.",
            "code": "REQUIRED"
        },
        {
            "propertyPath": "rating",
            "message": "Field 'rating' must not exceed 5.",
            "code": "MAX_VALUE"
        }
    ]
}
Copied!

PATCH behaviour 

On PATCH requests (partial updates), validation only runs on fields that are present in the request body. Absent fields — including required ones — are skipped. Auto-derived validators follow the same rule.

Custom validators 

When the built-in types are not enough, register your own rule by implementing \MaikSchneider\TcaApi\Validation\ValidatorInterface and referencing it by class-string as the validator type. Implementations are auto-discovered via the tca_api.validator DI tag — no Services.yaml entry is needed as long as your extension uses autoconfigure (the TYPO3 default).

'iban' => [
    'groups'     => ['list', 'show', 'create', 'update'],
    'validators' => [
        ['type' => \Acme\Validator\IbanValidator::class, 'options' => ['country' => 'DE']],
    ],
],
Copied!

The validator receives a typed \MaikSchneider\TcaApi\Validation\ValidationContext and returns a (possibly empty) list of \MaikSchneider\TcaApi\Validation\Violation objects:

use MaikSchneider\TcaApi\Validation\ValidationContext;
use MaikSchneider\TcaApi\Validation\ValidatorInterface;
use MaikSchneider\TcaApi\Validation\Violation;

final class IbanValidator implements ValidatorInterface
{
    public function validate(ValidationContext $context): array
    {
        if (is_valid_iban((string)$context->value, $context->option('country'))) {
            return [];
        }

        return [new Violation('Not a valid IBAN.', 'IBAN')];
    }
}
Copied!

ValidationContext exposes:

Property Description
value The submitted value of the column being validated.
column / table Column name and resource table.
options Per-validator options from the config; use option($key, $default).
body The full request body — enables cross-field rules.
partial true on PATCH requests.
resourceConfig The full ApiDefinition for the resource.

Each returned Violation carries a message and a machine-readable code; its propertyPath defaults to the validated column but can be set explicitly to point a violation at a different field. Violations merge into the same 422 Hydra error response as the built-in validators. Built-in and custom validators can be combined freely on the same column.