---
title: "File upload"
manual: "TYPO3 Explained"
version: "13.4"
permalink: "https://docs.typo3.org/permalink/t3coreapi:extbase-fileupload@13.4"
source: "ExtensionArchitecture/Extbase/Reference/FileUpload.rst"
rendered: "2026-10-01T12:09:27+00:00"
---

# File upload {#extbase-fileupload}

Implementing file uploads / attachments to Extbase domain models
has always been a bit of a challenge.

While it is straight-forward to access an existing file reference in a domain model,
writing new files to the [FAL (File Access Layer)](https://docs.typo3.org/permalink/t3coreapi:using-fal@13.4)
takes more effort.

## Accessing a file reference in an Extbase domain model {#extbase-fileupload-accessing}

You need two components for the structural information: the Domain
Model definition and the TCA entry.

The domain model definition:

**EXT:my_extension/Classes/Domain/Model/Blog.php**

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Domain\Model;

use TYPO3\CMS\Extbase\Domain\Model\FileReference;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;

class Blog extends AbstractEntity
{
  // A single file
  protected ?FileReference $singleFile = null;

  /**
   * A collection of files.
   * @var ObjectStorage<FileReference>
   */
  protected ObjectStorage $multipleFiles;

  // When using ObjectStorages, it is vital to initialize these.
  public function __construct()
  {
    $this->multipleFiles = new ObjectStorage();
  }

  /**
   * Called again with initialize object, as fetching an entity from the DB does not use the constructor
   */
  public function initializeObject(): void
  {
    $this->multipleFiles = $this->multipleFiles ?? new ObjectStorage();
  }

  // Typical getters
  public function getSingleFile(): ?FileReference
  {
    return $this->singleFile;
  }

  /**
   * @return ObjectStorage|FileReference[]
   */
  public function getMultipleFiles(): ObjectStorage
  {
    return $this->multipleFiles;
  }

  // For later examples, the setters:
  public function setSingleFile(?FileReference $singleFile): void
  {
    $this->singleFile = $singleFile;
  }

  public function setMultipleFiles(ObjectStorage $files): void
  {
    $this->multipleFiles = $files;
  }
}

```

and the TCA definition:

**EXT:my_extension/Configuration/TCA/tx_myextension_domain_model_blog.php**

```php
<?php

return [
  'ctrl' => [
    // .. usual TCA fields
  ],
  'columns' => [
    // ... usual TCA columns
    'single_file' => [
      'exclude' => true,
      'label' => 'Single file',
      'config' => [
        'type' => 'file',
        'maxitems' => 1,
        'allowed' => 'common-image-types',
      ],
    ],
    'multiple_files' => [
      'exclude' => true,
      'label' => 'Multiple files',
      'config' => [
        'type' => 'file',
        'allowed' => 'common-image-types',
      ],
    ],
  ],
];

```

Once this is set up, you can create/edit records through the TYPO3
backend (for example via **Web > List**), attach a single or
multiple files in it. Then using a normal
controller and Fluid template, you can display an image.

The relevant Extbase controller part:

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

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Controller;

use MyVendor\MyExtension\Domain\Model\Blog;
use MyVendor\MyExtension\Domain\Repository\BlogRepository;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class BlogController extends ActionController
{
  public function __construct(protected readonly BlogRepository $blogRepository)
  {
    // Note: The repository is a standard extbase repository, nothing specific
    //       to this example.
  }

  public function showAction(Blog $blog): ResponseInterface
  {
    $this->view->assign('blog', $blog);

    return $this->htmlResponse();
  }
}

```

and the corresponding Fluid template:

**EXT:my_extension/Resources/Private/Templates/Blog/Show.html**

```html
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
  data-namespace-typo3-fluid="true">

<f:layout name="Default" />

<f:section name="main">
  <p>Single image:</p>
  <f:image image="{blog.singleFile.originalFile}" />

  <p>Multiple images:</p>
  <f:for each="{blog.multipleFiles}" as="image">
    <f:image image="{image.originalFile}" />
  </f:for>

  <p>Access first image of multiple images:</p>
  <f:image image="{blog.multipleFiles[0].originalFile}" />
</f:section>

```

On the PHP side within controllers, you can use the usual
`$blogItem->getSingleFile()` and `$blogItem->getMultipleFiles()`
Extbase getters to retrieve the FileReference object.

## Writing FileReference entries {#extbase-fileupload-writing}

### Manual handling {#extbase-fileupload-writing-manual}

With TYPO3 versions below v13.3, attaching files to an Extbase domain model
was only possible by either:

-   Manually evaluate the `$_FILES` data, process and validate the data,
    use raw QueryBuilder write actions on `sys_file` and `sys_file_reference`
    to persist the files quickly, or use at least some API methods:

    **EXT:my_extension/Classes/Controller/BlogController.php, excerpt**

    ```php
    <?php

    declare(strict_types=1);

    namespace MyVendor\MyExtension\Controller;

    use MyVendor\MyExtension\Domain\Model\Blog;
    use MyVendor\MyExtension\Domain\Repository\BlogRepository;
    use TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior;
    use TYPO3\CMS\Core\Resource\ResourceFactory;
    use TYPO3\CMS\Core\Utility\GeneralUtility;
    use TYPO3\CMS\Core\Utility\StringUtility;
    use TYPO3\CMS\Extbase\Domain\Model\FileReference;
    use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

    class BlogController extends ActionController
    {
      public function __construct(
        protected ResourceFactory $resourceFactory,
        protected BlogRepository $blogRepository,
      ) {}

      public function attachFileUpload(Blog $blog): void
      {
        $falIdentifier = '1:/your_storage';
        $yourFile = '/path/to/uploaded/file.jpg';

        // Attach the file to the wanted storage
        $falFolder = $this->resourceFactory->retrieveFileOrFolderObject($falIdentifier);
        $fileObject = $falFolder->addFile(
          $yourFile,
          basename($yourFile),
          DuplicationBehavior::REPLACE,
        );

        // Initialize a new storage object
        $newObject = [
          'uid_local' => $fileObject->getUid(),
          'uid_foreign' => StringUtility::getUniqueId('NEW'),
          'uid' => StringUtility::getUniqueId('NEW'),
          'crop' => null,
        ];

        // Create the FileReference Object
        $fileReference = $this->resourceFactory->createFileReferenceObject($newObject);

        // Port the FileReference Object to an Extbase FileReference
        $fileReferenceObject = GeneralUtility::makeInstance(FileReference::class);
        $fileReferenceObject->setOriginalResource($fileReference);

        // Persist the created file reference object to our Blog model
        $blog->setSingleFile($fileReferenceObject);
        $this->blogRepository->update($blog);

        // Note: For multiple files, a wrapping ObjectStorage would be needed
      }
    }

    ```

    Instead of raw access to `$_FILES`, starting with TYPO3 v12 the recommendation
    is to utilize the [UploadedFile objects instead of $\_FILES](https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/12.0/Breaking-97214-UseUploadedFileObjectsInsteadOf_FILES.html#breaking-97214).
    In that case, validators can be used for custom UploadedFile objects to specify restrictions
    on file types, file sizes and image dimensions.
-   Use (or better: adapt) a more complex implementation by using Extbase TypeConverters,
    as provided by [Helmut Hummel's EXT:upload_example](https://github.com/helhum/upload_example).
    This extension is no longer maintained and will not work without larger adaptation for
    TYPO3 v12 compatibility.

### Automatic handling based on PHP attributes {#extbase-fileupload-writing-attributes}

Starting with TYPO3 v13.3 it is finally possible to streamline this with commonly
known Extbase logic, as implemented via
[Feature: #103511 - Introduce Extbase file upload and deletion handling](https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/13.3/Feature-103511-IntroduceExtbaseFileUploadHandling.html#feature-103511-1711894330).

An example implementation of this can be found on
[Torben Hansen's EXT:extbase-upload](https://github.com/derhansen/extbase_upload)
repository.

The general idea is to use PHP attributes within the Extbase Model, and for the upload
use a custom ViewHelper.

The domain model:

**EXT:my_extension/Classes/Domain/Model/Blog.php, using FileUpload attributes**

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Domain\Model;

use TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior;
use TYPO3\CMS\Extbase\Annotation\FileUpload;
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;

class Blog extends AbstractEntity
{
  // A single file
  #[FileUpload([
    'validation' => [
      'required' => true,
      'maxFiles' => 1,
      'fileSize' => ['minimum' => '0K', 'maximum' => '2M'],
      'mimeType' => [
        'allowedMimeTypes' => ['image/jpeg'],
        'ignoreFileExtensionCheck' => false,
        'notAllowedMessage' => 'LLL:EXT:my_extension/...',
        'invalidExtensionMessage' => 'LLL:EXT:my_extension/...',
      ],
      'imageDimensions' => ['maxWidth' => 4096, 'maxHeight' => 4096],
    ],
    'uploadFolder' => '1:/user_upload/extbase_single_file/',
    'addRandomSuffix' => true,
    'duplicationBehavior' => DuplicationBehavior::RENAME,
  ])]
  protected ?FileReference $singleFile = null;

  #[FileUpload([
    'validation' => [
      'required' => true,
      'fileSize' => ['minimum' => '0K', 'maximum' => '2M'],
      'mimeType' => [
        'allowedMimeTypes' => ['image/jpeg'],
        'ignoreFileExtensionCheck' => false,
        'notAllowedMessage' => 'LLL:EXT:my_extension/...',
        'invalidExtensionMessage' => 'LLL:EXT:my_extension/...',
      ],
    ],
    'uploadFolder' => '1:/user_upload/extbase_multiple_files/',
  ])]

  /**
   * A collection of files.
   * @var ObjectStorage<FileReference>
   */
  protected ObjectStorage $multipleFiles;

  // When using ObjectStorages, it is vital to initialize these.
  public function __construct()
  {
    $this->multipleFiles = new ObjectStorage();
  }

  /**
   * Called again with initialize object, as fetching an entity from the DB does not use the constructor
   */
  public function initializeObject(): void
  {
    $this->multipleFiles = $this->multipleFiles ?? new ObjectStorage();
  }

  // Typical getters
  public function getSingleFile(): ?FileReference
  {
    return $this->singleFile;
  }

  /**
   * @return ObjectStorage|FileReference[]
   */
  public function getMultipleFiles(): ObjectStorage
  {
    return $this->multipleFiles;
  }

  // Typical setters
  public function setSingleFile(?FileReference $singleFile): void
  {
    $this->singleFile = $singleFile;
  }

  public function setMultipleFiles(ObjectStorage $files): void
  {
    $this->multipleFiles = $files;
  }
}

```

and the corresponding Fluid template utilizing the ViewHelper:

**EXT:my_extension/Resources/Private/Templates/Blog/New.html**

```html
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
  xmlns:form="http://typo3.org/ns/TYPO3/CMS/Form/ViewHelpers"
  data-namespace-typo3-fluid="true">

<f:layout name="Default" />

<f:section name="main">
  <f:form action="create" name="blog" object="{blog}" enctype="multipart/form-data">
    <div>
      <p>Single file</p>
      <f:form.upload property="singleFile" />
    </div>

    <div>
      <p>Multiple files</p>
      <f:form.upload property="multipleFiles" multiple="1" />
    </div>

    <div>
      <f:form.submit value="Save" />
    </div>
  </f:form>
</f:section>

```

You can also allow to remove already uploaded files (for the user):

**EXT:my_extension/Resources/Private/Templates/Blog/New.html**

```html
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
  xmlns:form="http://typo3.org/ns/TYPO3/CMS/Form/ViewHelpers"
  data-namespace-typo3-fluid="true">

<f:layout name="Default" />

<f:section name="main">
  <f:form action="create" name="blog" object="{blog}" enctype="multipart/form-data">
    <div>
      <p>Single file</p>

      <f:if condition="{blog.singleFile}">
        <div>
          <f:form.uploadDeleteCheckbox id="singleFile"
            property="singleFile"
            fileReference="{blog.singleFile}" />
          <label for="singleFile">Delete file</label>
        </div>
      </f:if>

      <f:form.upload property="singleFile" />
    </div>

    <div>
      <p>Multiple files</p>

      <f:if condition="{blog.multipleFiles}">
        <f:for each="{blog.multipleFiles}" as="file" iteration="i">
          <div>
            <f:form.uploadDeleteCheckbox id="multipleFiles.{i.index}"
              property="multipleFiles"
              fileReference="{file}" />
            <label for="multipleFiles.{i.index}">Delete file</label>
          </div>
        </f:for>
      </f:if>

      <f:form.upload property="multipleFiles" multiple="1" />
    </div>

    <div>
      <f:form.submit value="Save" />
    </div>
  </f:form>
</f:section>

```

The controller action part with persisting the data needs no further custom code,
Extbase can automatically do all the domain model handling on its own. The TCA can
also stay the same as configured for simply read-access to a domain model. The only
requirement is that you take care of persisting the domain model after create/update
actions:

**EXT:my_extension/Resources/Private/Templates/Blog/New.html**

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Controller;

use MyVendor\MyExtension\Domain\Model\Blog;
use MyVendor\MyExtension\Domain\Repository\BlogRepository;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class BlogController extends ActionController
{
  public function __construct(protected readonly BlogRepository $blogRepository)
  {
    // Note: The repository is a standard extbase repository, nothing specific
    //       to this example.
  }

  public function listAction(): ResponseInterface
  {
    $this->view->assign('blog', $this->blogRepository->findAll());
    return $this->htmlResponse();
  }

  public function newAction(): ResponseInterface
  {
    // Create a fresh domain model for CRUD
    $this->view->assign('blog', GeneralUtility::makeInstance(Blog::class));
    return $this->htmlResponse();
  }

  public function createAction(Blog $blog): ResponseInterface
  {
    // Set some basic attributes to your domain model that users should not
    // influence themselves, like the storage PID
    $blog->setPid(42);

    // Persisting is needed to properly create FileReferences for the File object
    $this->blogRepository->add($blog);

    return $this->redirect('list');
  }

  public function editAction(?Blog $blog): ResponseInterface
  {
    $this->view->assign('blog', $blog);
    return $this->htmlResponse();
  }

  public function updateAction(Blog $item): ResponseInterface
  {
    $this->blogRepository->update($item);
    return $this->redirect('list');
  }
}

```

The actual file upload processing is done after extbase property mapping was successful.
If not all properties of a domain model are valid, the file will not be uploaded.
This means, if any error occurs, a user will have to re-upload a file.

The implementation is done like this to prevent stale temporary files that would
need cleanup or could raise issues with Denial of Service (by filling up disk-space
with these temporarily uploaded files).

> [!NOTE]
> File upload handling for nested domain models (e.g. modelA.modelB.fileReference)
> is not supported.

> [!IMPORTANT]
> When working with file uploads in domain models, it is required to persist the
> model within the same request in your Controller of the target action, for example
> via `$myRepository->add($myModel)`. Otherwise, dangling `sys_file` records will
> be created, without a `FileReference` in place, leading to stale temporary
> files that will need cleanup.

## Reference for the `FileUpload` PHP attribute {#extbase-fileupload-attribute}

File uploads can be validated by the following rules:

-   minimum and maximum file count
-   minimum and maximum file size
-   allowed MIME types
-   image dimensions (for image uploads)

Additionally, it is ensured, that the filename given by the client is valid,
meaning that no invalid characters (null-bytes) are added and that the file
does not contain an invalid file extension. The API has support for custom
validators, which can be created on demand.

To avoid complexity and maintain data integrity, a file upload is only
processed if the validation of all properties of a domain model is successful.
In this first implementation, file uploads are not persisted/cached temporarily,
so this means in any case of a validation failure ("normal" validators and file upload
validation) a file upload must be performed again by users.

Possible future enhancements of this functionality could enhance the existing
`#[FileUpload]` attribute/annotation with configuration like a temporary storage
location, or specifying additional custom validators (which can be done via the PHP-API as
described below)

## File upload configuration with the `FileUpload` attribute {#extbase-fileupload-attribute-configuration}

File upload for a property of a domain model can be configured using the
newly introduced `\TYPO3\CMS\Extbase\Annotation\FileUpload` attribute.

Example:

**EXT:my_extension/Classes/Domain/Model/Blog.php (example excerpt of an Extbase domain model)**

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Domain\Model;

use TYPO3\CMS\Extbase\Annotation\FileUpload;
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;

class Blog extends AbstractEntity
{
  #[FileUpload([
    'validation' => [
      'required' => true,
      'maxFiles' => 1,
      'fileSize' => ['minimum' => '0K', 'maximum' => '2M'],
      'mimeType' => [
        'allowedMimeTypes' => ['image/jpeg', 'image/png'],
        'ignoreFileExtensionCheck' => false,
        'notAllowedMessage' => 'LLL:EXT:my_extension/...',
        'invalidExtensionMessage' => 'LLL:EXT:my_extension/...',
      ],
      'fileExtension' => ['allowedFileExtensions' => ['jpg', 'jpeg', 'png']],
    ],
    'uploadFolder' => '1:/user_upload/files/',
  ])]
  protected ?FileReference $file = null;

  // getters and setters like usual
}

```

All configuration settings of the
`\TYPO3\CMS\Extbase\Mvc\Controller\FileUploadConfiguration` object can
be defined using the `\TYPO3\CMS\Extbase\Annotation\FileUpload`
attribute. It is however not possible
to add custom validators using the
`\TYPO3\CMS\Extbase\Annotation\FileUpload` attribute, which you
can achieve with a manual configuration as shown below.

The currently available configuration array keys are:

-   `validation` (`array` with keys `required`, `maxFiles`, `minFiles`,
    `fileSize`, `mimeType`, `allowedMimeTypes`, `fileExtension`, `imageDimensions`, see
    [File upload validation](https://docs.typo3.org/permalink/t3coreapi:extbase-fileupload-attribute-validationkeys@13.4))
-   `uploadFolder` (`string`, destination folder)
-   `duplicationBehavior` (`object`, behaviour when file exists)
-   `addRandomSuffix` (`bool`, suffixing files)
-   `createUploadFolderIfNotExist` (`bool`, whether to create missing
    directories)

It is also possible to use the `\TYPO3\CMS\Extbase\Annotation\FileUpload` attribute to configure
file upload properties, but it is recommended to use the
`\TYPO3\CMS\Extbase\Annotation\FileUpload` attribute due to better readability.

## Manual file upload configuration {#extbase-fileupload-attribute-manual-configuration}

A file upload configuration can also be created manually and should be
done in the `initialize*Action`.

Example:

**EXT:my_extension/Classes/Controllers/ExampleController.php (example excerpt of an Extbase Controller)**

```php
<?php

declare(strict_types=1);

use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use TYPO3\CMS\Extbase\Mvc\Controller\FileUploadConfiguration;
use TYPO3\CMS\Extbase\Validation\Validator\FileExtensionValidator;
use TYPO3\CMS\Extbase\Validation\Validator\MimeTypeValidator;

class SomeController extends ActionController
{
  public function initializeCreateAction(): void
  {
    // As Validators can contain state, do not inject them
    $mimeTypeValidator = GeneralUtility::makeInstance(MimeTypeValidator::class);
    $mimeTypeValidator->setOptions([
      'allowedMimeTypes' => ['image/jpeg'],
      'ignoreFileExtensionCheck' => false,
      'notAllowedMessage' => 'LLL:EXT:my_extension/...',
      'invalidExtensionMessage' => 'LLL:EXT:my_extension/...',
    ]);

    $fileExtensionValidator = GeneralUtility::makeInstance(FileExtensionValidator::class);
    $fileExtensionValidator->setOptions([
      'allowedFileExtensions' => ['jpg', 'jpeg'],
    ]);

    $fileHandlingServiceConfiguration = $this->arguments->getArgument('myArgument')->getFileHandlingServiceConfiguration();
    $fileHandlingServiceConfiguration->addFileUploadConfiguration(
      (new FileUploadConfiguration('myPropertyName'))
            ->setRequired()
            ->addValidator($mimeTypeValidator)
            ->addValidator($fileExtensionValidator)
            ->setMaxFiles(1)
            ->setUploadFolder('1:/user_upload/files/'),
    );

    // Extbase's property mapping is not handling FileUploads, so it must not operate on this property.
    // When using the FileUpload attribute/annotation, this internally does the same. This is covered
    // by the `addFileUploadConfiguration()` functionality.
    $this->arguments->getArgument('myArgument')->getPropertyMappingConfiguration()->skipProperties('myPropertyName');
  }
}

```

## Configuration options for file uploads {#extbase-fileupload-attribute-options}

The configuration for a file upload is defined in a
`FileUploadConfiguration` object.

This object contains the following configuration options.

> [!TIP]
> **Hint**
>
> The appropriate setter methods or configuration
> keys can best be inspected inside that class definition.

#### Property name: {#extbase-fileupload-attribute-property-name}

Defines the name of the property of a domain model to which the file upload
configuration applies. The value is automatically retrieved when using
the `\TYPO3\CMS\Extbase\Annotation\FileUpload` attribute. If the
`\TYPO3\CMS\Extbase\Mvc\Controller\FileUploadConfiguration` object
is created manually, it must be set using the `$propertyName`
constructor argument.

#### Validation: {#extbase-fileupload-attribute-validation}

File upload validation is defined in an array of validators in the
`\TYPO3\CMS\Extbase\Mvc\Controller\FileUploadConfiguration` object.

The validators
`\TYPO3\CMS\Extbase\Validation\Validator\FileNameValidator`,
(ensures that no executable PHP files can
be uploaded) and `\TYPO3\CMS\Extbase\Validation\Validator\FileExtensionMimeTypeConsistencyValidator`
(ensuring that the file extension matches the expected mime-type assumptions),
are enforced and executed by default.

In addition, Extbase includes the following validators to validate an
`\TYPO3\CMS\Core\Http\UploadedFile` object:

-   `\TYPO3\CMS\Extbase\Validation\Validator\FileSizeValidator`
-   `\TYPO3\CMS\Extbase\Validation\Validator\MimeTypeValidator`
-   `\TYPO3\CMS\Extbase\Validation\Validator\ImageDimensionsValidator`
-   `\TYPO3\CMS\Extbase\Validation\Validator\FileExtensionValidator`

Those validators can either be configured with the
`\TYPO3\CMS\Extbase\Annotation\FileUpload` attribute or added
manually to the configuration object
with the `addValidator()` method.

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

The validators FileExtensionValidator and FileExtensionMimeTypeConsistencyValidator have been
added to provide better integration with the security feature
Important: #106240 - Enforce File Extension and MIME-Type Consistency in File Abstraction Layer.

#### Required {#extbase-fileupload-attribute-required}

Defines whether a file must be uploaded. If it is set to `true`, the
`minFiles` configuration is set to `1`.

#### Minimum files {#extbase-fileupload-attribute-minimum-files}

Defines the minimum amount of files to be uploaded.

#### Maximum files {#extbase-fileupload-attribute-maximum-files}

Defines the maximum amount of files to be uploaded.

#### Upload folder {#extbase-fileupload-attribute-upload-folder}

Defines the upload path for the file upload. This configuration expects a
storage identifier (e.g. `1:/user_upload/folder/`). If the given target
folder in the storage does not exist, it is created automatically.

#### Upload folder creation, when missing {#extbase-fileupload-attribute-upload-folder-creation}

The default creation of a missing storage folder can be disabled via the
property `createUploadFolderIfNotExist` of the `#[FileUpload([...])]` attribute
(`bool`, default `true`).

#### Add random suffix {#extbase-fileupload-attribute-random-suffix}

When enabled, the filename of an uploaded and persisted file will contain a
random 16 char suffix. As an example, an uploaded file named
`job-application.pdf` will be persisted as
`job-application-<random-hash>.pdf` in the upload folder.

The default value for this configuration is `true` and it is recommended
to keep this configuration active.

This configuration only has an effect when uploaded files are persisted.

#### Duplication behavior {#extbase-fileupload-attribute-duplication-behavior}

Defines the FAL behavior, when a file with the same name exists in the target
folder. Possible values are
`\TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior::RENAME` (default),
`\TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior::REPLACE` and
`\TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior::CANCEL`.

## Modifying existing configuration {#extbase-fileupload-attribute-configuration-change}

File upload configuration defined by the
`\TYPO3\CMS\Extbase\Annotation\FileUpload` attribute can be
changed in the `initialize*Action`.

Example:

**Excerpt of an Extbase controller class**

```php
public function initializeCreateAction(): void
{
    $validator = GeneralUtility::makeInstance(MyCustomValidator::class);

    $argument = $this->arguments->getArgument('myArgument');
    $configuration = $argument->getFileHandlingServiceConfiguration()->getFileUploadConfigurationForProperty('file');
    $configuration?->setMinFiles(2);
    $configuration?->addValidator($validator);
    $configuration?->setUploadFolder('1:/user_upload/custom_folder');
}
```

The example shows how to modify the file upload configuration for the argument
`item` and the property `file`. The minimum amount of files to be
uploaded is set to `2` and a custom validator is added.

To remove all defined validators except the mandatory `FileNameValidator`
and `FileExtensionMimeTypeConsistencyValidator, use the :php:`resetValidators()\` method.

## Using TypoScript configuration for file uploads configuration {#extbase-fileupload-attribute-typoscript}

When a file upload configuration for a property has been added using the
`\TYPO3\CMS\Extbase\Annotation\FileUpload` attribute, it may be
required make the upload folder or
other configuration options configurable with TypoScript.

Extension authors should use the `initialize*Action` to apply settings
from TypoScript to a file upload configuration.

Example:

**Exercept of an Extbase controller class**

```php
public function initializeCreateAction(): void
{
    $argument = $this->arguments->getArgument('myArgument');
    $configuration = $argument->getFileHandlingServiceConfiguration()->getConfigurationForProperty('file');
    $configuration?->setUploadFolder($this->settings['uploadFolder'] ?? '1:/fallback_folder');
}
```

## File upload validation {#extbase-fileupload-attribute-validationkeys}

Each uploaded file can be validated against a configurable set of validators.
The `validation` section of the `\TYPO3\CMS\Extbase\Annotation\FileUpload` attribute allows to
configure commonly used validators using a configuration shorthand.

The following validation rules can be configured in the `validation`
section of the `\TYPO3\CMS\Extbase\Annotation\FileUpload` attribute:

-   `required`
-   `minFiles`
-   `maxFiles`
-   `fileSize`  (for `\TYPO3\CMS\Extbase\Validation\Validator\FilesizeValidator`)
-   `imageDimensions` (for `\TYPO3\CMS\Extbase\Validation\Validator\ImageDimensionsValidator`)
-   `fileExtension`  (for `\TYPO3\CMS\Extbase\Validation\Validator\FileExtensionValidator`)
-   `mimeType` (for `\TYPO3\CMS\Extbase\Validation\Validator\MimeTypeValidator`)
-   `allowedMimeTypes` (shorthand notation for configuration option `allowedMimeTypes` of the `MimeTypeValidator`, see below)

Example:

**Excerpt of an attribute within an Extbase domain model class**

```php
#[FileUpload([
    'validation' => [
        'required' => true,
        'maxFiles' => 1,
        'fileSize' => ['minimum' => '0K', 'maximum' => '2M'],
        'mimeType' => [
            'allowedMimeTypes' => ['image/jpeg'],
            'ignoreFileExtensionCheck' => false,
            'notAllowedMessage' => 'LLL:EXT:my_extension/...',
            'invalidExtensionMessage' => 'LLL:EXT:my_extension/...',
        ],
        'fileExtension' => ['allowedFileExtensions' => ['jpg', 'jpeg']],
        'imageDimensions' => ['maxWidth' => 4096, 'maxHeight' => 4096]
    ],
    'uploadFolder' => '1:/user_upload/extbase_single_file/',
])]
```

Extbase will internally use the Extbase file upload validators for
`fileExtensionMimeTypeConsistency`, `fileExtension`, `fileSize`, `mimeType`
and `imageDimensions` validation.

All options of the `\TYPO3\CMS\Extbase\Validation\Validator\MimeTypeValidator` can be set
by the `mimeType` array key.

> [!TIP]
> **Hint**
>
> It is recommended to always set the list of allowed `allowedMimeTypes` as well as
> `allowedFileExtensions` to ensure the uploaded data adheres to the expected uploaded data,
> so you can be sure to operate on the proper file types later on. The same list should
> be specified for the `<f:form.upload accept="...">` ViewHelper, so the user's client will already
> only be able to upload these types of files for a better user experience. Also remember the
> [TCA type=file 'allowed' key](https://docs.typo3.org/m/typo3/reference-tca/13.4/en-us/ColumnsConfig/Type/File/Index.html#columns-file) also lists
> allowed file extensions for backend operations (not evaluated by Extbase).

> [!NOTE]
> The TYPO3 feature flags `security.system.enforceFileExtensionMimeTypeConsistency` and
> `security.system.enforceAllowedFileExtensions` are **not** evaluated in Extbase context.
> They are bypassed via specific `skipInstructions` of the MIME Type Consistency Service.
> The mandatory validator `fileExtensionMimeTypeConsistency` ensures to only allow file
> uploads with matching MIME types to the file contents. This is another reason why proper
> configuration of `allowedMimeTypes` and/or `allowedFileExtensions` is important to ensure
> only storing expected file types.

Custom validators can be created according to project requirements and must
extend the Extbase `\TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator`.
The value to be validated is
always a PSR-7 `\TYPO3\CMS\Core\Http\UploadedFile` object.
Custom validators can however not
be used in the `\TYPO3\CMS\Extbase\Annotation\FileUpload` attribute
and must be configured manually as shown in [Manual file upload configuration](https://docs.typo3.org/permalink/t3coreapi:extbase-fileupload-attribute-manual-configuration@13.4).

#### Shorthand notation for `allowedMimeTypes` {#extbase-fileupload-attribute-validationkeys-shorthand-allowedmimetypes}

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

The main validation array key mimeType with all available subkeys was added to replace
the restricted shorthand notation allowedMimeTypes. The former option only allowed to
configure allowedMimeTypes and no other subkeys.

Using the `mimeType` configuration array, all options of the `MimeTypeValidator`
can be set as sub-keys:

```php
#[FileUpload([
    'validation' => [
        'required' => true,
        'mimeType' => [
            'allowedMimeTypes' => ['image/jpeg'],
            'ignoreFileExtensionCheck' => false,
            'notAllowedMessage' => 'LLL:EXT:my_extension/...',
            'invalidExtensionMessage' => 'LLL:EXT:my_extension/...',
        ],
    ],
    'uploadFolder' => '1:/user_upload/files/',
])]
```

The shorthand notation via `'allowedMimeTypes'` continues to
exist, in case only the mime type validation is needed. However, it is recommended
to utilize the full `'mimeType'` configuration array.

## Deletion of uploaded files and file references {#extbase-fileupload-attribute-deletion}

The new Fluid ViewHelper
[Form.uploadDeleteCheckbox ViewHelper \<f:form.uploadDeleteCheckbox>](https://docs.typo3.org/other/typo3/view-helper-reference/13.4/en-us/Global/Form/UploadDeleteCheckbox.html#typo3-fluid-form-uploaddeletecheckbox)
can be used to show a "delete file" checkbox in a form.

Example for object with `\TYPO3\CMS\Core\Resource\FileReference` property:

**Fluid code example**

```php
<f:form.uploadDeleteCheckbox property="file" fileReference="{object.file}" />
```

Example for an object with an `ObjectStorage<FileReference>` property,
containing multiple files and allowing to delete the first one
(iteration is possible within Fluid, to do that for every object of the collection):

**Fluid code example**

```php
<f:form.uploadDeleteCheckbox property="file.0" fileReference="{object.file}" />
```

Extbase will then handle file deletion(s) before persisting a validated
object. It will:

-   validate that minimum and maximum file upload configuration for the affected
    property is fulfilled (only if the property has a `\TYPO3\CMS\Extbase\Annotation\FileUpload`)
-   delete the affected `sys_file_reference` record
-   delete the affected file

Internally, Extbase uses `FileUploadDeletionConfiguration` objects to track
file deletions for properties of arguments. Files are deleted directly without
checking whether the current file is referenced by other objects.

Apart from using this ViewHelper, it is of course still possible to manipulate
`\TYPO3\CMS\Core\Resource\FileReference` properties with custom logic before persistence.

#### ModifyUploadedFileTargetFilenameEvent {#extbase-fileupload-attribute-psr-event}

The `\TYPO3\CMS\Extbase\Event\Service\ModifyUploadedFileTargetFilenameEvent`
allows event listeners to
alter a filename of an uploaded file before it is persisted.

Event listeners can use the method `getTargetFilename()` to retrieve the filename
used for persistence of a configured uploaded file. The filename can then be
adjusted via `setTargetFilename()`. The relevant configuration can be retrieved
via `getConfiguration()`.

## Multi-step form handling {#extbase-fileupload-multistep}

The implementation of the file upload feature in Extbase intentionally requires to
handle the FileUpload directly within the validation/persistence step of a controller
action.

If you have a multi-step process in place, where the final persistence of a domain model
object is only performed later on, you will need to deal with the created files.

For example, you may want to implement a preview before the final domain model
entity is persisted.

Some possible ways to deal with this:

-   Implement the file handling as a [DTO](https://docs.typo3.org/permalink/t3coreapi:concept-dto@13.4). The key idea here is
    to decouple the uploaded file into its own domain model object. You can pass that
    along (including its persistence identity) from one form step to the next, and only
    in the final step you would take care of transferring the data of this DTO into
    your actual domain model, and attach the `FileReference` object.
-   Or use client-side JavaScript. You could create a stub in your Fluid template that
    has placeholders for user-specified data, and then fills the actual data (before
    the form is submitted) into these placeholders. You can use the JavaScript `FileReader()`
    object to access and render uploaded files.
