---
title: "Breaking: #110762 - File storages are configured in a file instead of the database"
manual: "TYPO3 Core Changelog"
version: "main"
permalink: "https://docs.typo3.org/permalink/changelog:breaking-110762-1789862500"
source: "Changelog/15.0/Breaking-110762-FileStoragesAreConfiguredInAFileInsteadOfTheDatabase.rst"
typo3-version: "15.0"
typo3-major: 15
type: "breaking"
issue: 110762
forge: "https://forge.typo3.org/issues/110762"
tags: ["FAL", "Database", "TCA", "PHP-API", "PartiallyScanned", "ext:core"]
rendered: "2026-10-27T20:58:30+00:00"
---

# Breaking: #110762 - File storages are configured in a file instead of the database {#breaking-110762-1789862500}

See [forge#110762](https://forge.typo3.org/issues/110762)

## Description {#description}

A file storage answers the question where TYPO3 reads files from, with
which driver and with which credentials. That is infrastructure, decided when
an installation is set up and usually changed together with a deployment. It
is not content that editors produce. TYPO3 nevertheless stored it as a record
in the database table `sys_file_storage` since the File Abstraction Layer
was introduced with TYPO3 6.0, edited through FormEngine like a content
element. What was the pragmatic choice at that time turned into three problems
that the Core of today can solve:

-   **A storage definition is not for regular admins.** It decides which
    directories of the server and which remote buckets TYPO3 reads, writes
    and deletes in, and which credentials it uses to do so. Changing the base
    path of a local storage to a directory outside the web root, or pointing
    it at another site on the same server, exposes and destroys data that no
    backend permission was ever meant to cover. Still, any backend user with
    `tables_modify` on the table could edit it, and the shipped editor
    permission preset granted exactly that. The definition now lives in
    `config/system/`, next to [`settings.php`](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/Configuration/Typo3ConfVars/Index.html#file-project-config-system-settings-php), and is only
    reachable for whoever maintains that directory: system maintainers,
    deployment tooling and upgrade code.
-   **A storage definition depends on the environment.** Credentials, bucket
    names and base paths legitimately differ between local, staging and
    production, and belong to the deployment, not to a database dump that is
    synchronized between them. A database row cannot reference an environment
    variable. The file can: `%env()%` placeholders and `imports`
    work as in the site configuration, so the definition is versioned,
    reviewed and deployed with the project, with the secrets kept outside.
-   **A storage definition starts at the project root.** The base path of a
    local storage was relative to the public web directory, so a storage
    outside the web root, the natural place for files that must never be
    served directly, could only be configured with an absolute path that ties
    the definition to one server. Relative base paths are now resolved
    against the project path, see [Feature: #110762 - Project-root relative base paths for local storages](https://docs.typo3.org/permalink/changelog:feature-110762-1789250000):
    `public/fileadmin/` for the default storage of a Composer based
    installation, `storage/private/` for a directory next to
    `config/` and `var/`.

File storages are therefore configured in the file
`config/system/file-storages.yaml`
(`typo3conf/system/file-storages.yaml` in classic mode). The database
table `sys_file_storage`, its TCA and the record form are removed.
There is no backend form to edit the file.

The file is optional. Without it, TYPO3 assumes exactly the storage it
created automatically until now: one local storage with identifier `1`
for the directory configured in
`$GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir']`. Installations with
only this storage need no file at all.

**config/system/file-storages.yaml**

```yaml
storages:
  1:
    name: fileadmin
  2:
    name: 'Assets (S3)'
    driver: AmazonS3
    public: false
    configuration:
      bucket: '%env(S3_BUCKET)%'
      key: '%env(S3_KEY)%'
      secret: '%env(S3_SECRET)%'
```

The map key is the storage identifier, the former `uid`. It stays
numeric because every persisted reference, from `sys_file.storage` to
combined identifiers like `1:/user_upload/`, uses it. Do not reuse the
identifier of a removed storage: file mounts, upload folders in TSconfig and
links may still point to it. Each storage takes the following options, all
optional:

| Option | Default | Former column |
| --- | --- | --- |
| `name` | `Storage <identifier>` | `name` |
| `driver` | `Local` | `driver` |
| `default` | `true` for a single storage, else `false` | `is_default`. Exactly one storage must be the default when several are configured. |
| `browsable` | `true` | `is_browsable` |
| `public` | `true` | `is_public` |
| `writable` | `true` | `is_writable` |
| `online` | `true` | `is_online`. A maintainer decision only, the runtime fuse of failing drivers lives in the `hash` cache. |
| `autoExtractMetadata` | `true` | `auto_extract_metadata` |
| `processingFolder` | empty, meaning `_processed_/` inside the storage | `processingfolder` |
| `configuration` | empty map | `configuration`, the former FlexForm, as a plain map of driver options |

The file is loaded through the YAML loader of TYPO3, so `%env()%`
placeholders and `imports` work. Placeholders are kept when TYPO3
writes the file itself. Storages defined in an imported file are changed in
that file; TYPO3 refuses to write them into the main file. Comments and YAML
anchors of the main file are not preserved when TYPO3 writes it.

The base path of a local storage is either absolute or relative to the
project path. The former option `pathType: relative` is still
understood and resolves against the public path, new entries should not use
it.
When the option `caseSensitive` is not set, the local driver detects the
behaviour of the file system below the base path.

Reading the file is tolerant, so that a single mistake does not stop every
file access of the site. Each problem is logged:

-   Unknown options of a storage are ignored, so a file written for a newer
    TYPO3 version still works.
-   A storage with an invalid value, or with an `%env()%` placeholder
    whose environment variable is not set, is available as an offline
    storage.
-   If several storages are marked as default, the first of them is the
    default storage; if none is, the first storage is.

A YAML syntax error, a non-numeric identifier or a storage that is not a map
of options are errors that stop file access. TYPO3 itself validates
strictly when it writes the file.

The evaluated file is kept in the `core` cache, keyed by the content of
the file. A changed file takes effect right away; after changing an imported
file or an environment variable used by a placeholder, flush the caches.

### Migration of existing records {#migration-of-existing-records}

The upgrade wizard "Move file storage records to
config/system/file-storages.yaml" writes the rows of `sys_file_storage`
into the file:

-   The FlexForm of each record becomes the `configuration` map. Local
    base paths that were relative to the public directory are rewritten into
    paths relative to the project path. The `uid` stays the identifier.
-   Deleted records are skipped. If no record is marked as default, the first
    one becomes the default; if several are, the first of them stays the
    default.
-   An installation whose only record is the auto-created `fileadmin`
    storage with identifier `1` gets no file.
-   Records of a table that the database analyzer already renamed to
    `zzz_deleted_sys_file_storage` are migrated as well.
-   If the file defines storages already, for example because it was
    committed from another environment, the wizard does not change it. It
    lists the storages of the database that are missing in the file and stays
    open until they are added or removed from the database.
-   Driver configuration of other drivers than `Local` is written as
    found, credentials included. The wizard points this out.

The file is part of the deployment, so the wizard should run where the
result can be committed, usually in the local development environment:

1.  Run the upgrade wizard.
1.  Replace credentials in the file with `%env()%` placeholders and
    provide the variables in every environment.
1.  Commit the file and deploy it.
1.  Run the wizard on every further environment. It reports storages of that
    database that are missing in the deployed file.
1.  Remove the table with the database analyzer.

Production systems usually have no version control and are often
deployed read-only, while their database is the one that holds the storage
records that matter. Do not run the wizard there first. Instead, import a
recent copy of the production database into the local or staging
environment, run the wizard there, and commit and deploy the resulting file
before upgrading production. The wizard on production then only confirms
that the deployed file covers all storages. If the wizard has already
written the file on a production system, copy it from there into the
project before the next deployment replaces `config/system/`.

The wizard is only offered when a file is needed. It is not shown when the
table does not exist (anymore), when it holds no active records, or when its
only record is the auto-created `fileadmin` storage with identifier
`1` and unchanged settings. Such installations, the majority, need no
file, no commit and no extra step in their deployment.

## Impact {#impact}

-   The database table `sys_file_storage`, its TCA, the record form and
    the labels of its columns are removed. Records of the table can no longer
    be created, edited, exported or referenced by the reference index. The
    table is dropped by the database analyzer after the upgrade wizard ran.
    Select fields of other tables that used
    `foreign_table => 'sys_file_storage'` have to use the items processor
    function `\TYPO3\CMS\Core\Hooks\TcaItemsProcessorFunctions->populateFileStorages()` instead,
    which lists the storages the current backend user may access.
-   The permission presets no longer grant `tables_select` and
    `tables_modify` for the table. Backend users cannot change storages
    anymore; which storages a user sees is defined by file mounts alone.
-   `\TYPO3\CMS\Core\Resource\StorageRepository` reads the file instead
    of the database. Its constructor takes
    `\TYPO3\CMS\Core\Resource\Configuration\FileStorageConfiguration` and
    `\TYPO3\CMS\Core\Resource\Configuration\FileStorageConfigurationWriter`
    in addition to `\TYPO3\CMS\Core\Database\ConnectionPool` and `\TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools`.
    The new method `addLocalStorage()` appends a local storage to the
    file. `createLocalStorage()` keeps its signature, writes the file as
    well and is deprecated, see [Deprecation: #110762 - StorageRepository->createLocalStorage() and legacy keys](https://docs.typo3.org/permalink/changelog:deprecation-110762-1791443860).
-   `\TYPO3\CMS\Core\Resource\ResourceStorage` receives the normalized
    storage definition instead of a database row: the keys are
    `identifier`, `name`, `driver`,
    `default`, `browsable`, `public`, `writable`,
    `online`, `autoExtractMetadata`, `processingFolder` and
    `configuration` (always an array). The same array is passed to
    `\TYPO3\CMS\Core\Resource\Event\BeforeResourceStorageInitializationEvent`
    and returned by `getRecord()`. Listeners that set the keys of the
    former row are mapped for one major version and trigger a deprecation,
    see [Deprecation: #110762 - StorageRepository->createLocalStorage() and legacy keys](https://docs.typo3.org/permalink/changelog:deprecation-110762-1791443860). Listeners that read the former
    keys from `getRecord()` get nothing and have to be adapted.
    `getStorageRecord()` is deprecated, see
    [Deprecation: #110762 - ResourceStorage->getStorageRecord()](https://docs.typo3.org/permalink/changelog:deprecation-110762-1789862501).
-   `\TYPO3\CMS\Core\Resource\ResourceStorage->markAsPermanentlyOffline()` no longer writes any
    record. It marks the storage as offline in the `hash` cache until
    the caches are flushed. A storage that has to stay offline is configured
    with `online: false`.
-   `\TYPO3\CMS\Core\Resource\Driver\DriverRegistry->addDriversToTCA()`
    is removed and the `flexFormDS` key of
    `$GLOBALS['TYPO3_CONF_VARS']['SYS']['fal']['registeredDrivers']` is
    no longer evaluated. A driver that still registers one triggers a
    deprecation log entry pointing to this document. Driver registration
    itself is unchanged: drivers keep registering a class, a short name and
    a label. The driver of a storage receives the `configuration` map
    of the file as its configuration array, exactly as it received the
    parsed FlexForm before.
-   The FormEngine node `userSysFileStorageIsPublic` and the class
    `\TYPO3\CMS\Backend\Form\Element\UserSysFileStorageIsPublicElement`
    are removed, as is the method
    `\TYPO3\CMS\Backend\Form\FormDataProvider\AbstractItemProvider->removeItemsByUserStorageRestriction()`.
-   Storage root nodes of the file tree now carry `recordType`
    `sys_file` and `resourceType` `storage`; their context
    menu offers the folder actions instead of the record actions of the
    former storage record.
-   The fallback storage with identifier `0` is not part of the file and
    stays as it is; it is scheduled for removal separately.

## Affected installations {#affected-installations}

Every installation. Installations with only the auto-created
`fileadmin` storage work without any change. Installations with
further storages have to run the upgrade wizard, after which the table can
be dropped. Extensions that read or write `sys_file_storage` records
directly, construct `\TYPO3\CMS\Core\Resource\ResourceStorage` objects from database rows, read
the former keys in a listener of
`\TYPO3\CMS\Core\Resource\Event\BeforeResourceStorageInitializationEvent` or provide a
`flexFormDS` for their driver are affected. The extension scanner
reports the removed and changed methods.

## Migration {#migration}

Run the upgrade wizard as described above, then let the database analyzer
remove the table. Edit `config/system/file-storages.yaml` by hand for
any further change, or use `\TYPO3\CMS\Core\Resource\Configuration\FileStorageConfigurationWriter` from
command line tools and set up code. Writing the file during regular web
requests is not supported: it fails on read-only file systems and diverges
between several web servers. Replace database access to
`sys_file_storage` with `\TYPO3\CMS\Core\Resource\StorageRepository`, which offers
`findAll()`, `findByUid()`, `getDefaultStorage()` and
`findByStorageType()`. Driver extensions
delete the `flexFormDS` entry and document their options for the
`configuration` map instead.
