PHPackages                             b13/db-file-storage - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [File &amp; Storage](/categories/file-storage)
4. /
5. b13/db-file-storage

ActiveTypo3-cms-extension[File &amp; Storage](/categories/file-storage)

b13/db-file-storage
===================

Store uploaded files directly in the TYPO3 database. A tiny, injectable service usable from Extbase and any PSR-15 / ActionController.

07PHPCI passing

Since Apr 9Pushed 2mo agoCompare

[ Source](https://github.com/b13/db_file_storage)[ Packagist](https://packagist.org/packages/b13/db-file-storage)[ RSS](/packages/b13-db-file-storage/feed)WikiDiscussions main Synced 2w ago

READMEChangelogDependenciesVersions (1)Used By (0)

db\_file\_storage
=================

[](#db_file_storage)

Store uploaded files directly in the TYPO3 database.

A tiny, injectable service that persists files into a single DB table (with a `LONGBLOB` content column) and hands them back out as PSR-7 responses. Works from Extbase controllers, PSR-15 middlewares, CLI commands, or anything the DI container builds.

- **TYPO3:** v13 LTS / v14
- **PHP:** 8.2+
- **Composer:** `b13/db-file-storage`

Installation
------------

[](#installation)

```
composer require b13/db-file-storage
vendor/bin/typo3 extension:setup
```

API
---

[](#api)

### Service — `DatabaseFileStorage`

[](#service--databasefilestorage)

The main entry point. Inject via constructor:

```
public function store(UploadedFileInterface $file): StoredFile;
public function storeContents(string $filename, string $contents, ?string $mimeType = null): StoredFile;
public function get(int $uid): ?StoredFile;
public function require(int $uid): StoredFile; // throws FileNotFoundException
public function delete(int $uid): bool; // soft-delete (sets deleted=1)
public function createResponse(int $uid, bool $forceDownload = false): ResponseInterface;
```

`StoredFile` is an immutable value object (`uid`, `filename`, `mimeType`, `size`, `sha1`, `contents`, `createdAt`). Every method after `store*()` takes the `uid` returned by it.

### Extbase entity — `StoredFileReference`

[](#extbase-entity--storedfilereference)

A metadata-only Extbase entity for the same table, suitable for `ObjectStorage` properties and MM relations. Does **not**map the `content` LONGBLOB — bytes stay behind `DatabaseFileStorage::get()`.

Ships with a repository (`StoredFileReferenceRepository`), a minimal `hideTable` / `readOnly` TCA stub, and a `Persistence/Classes.php` mapping that's auto-merged into every consumer extension.

Example: Extbase controller
---------------------------

[](#example-extbase-controller)

```
use B13\DbFileStorage\Service\DatabaseFileStorage;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

final class InvoiceController extends ActionController
{
    public function __construct(
        private readonly DatabaseFileStorage $databaseFileStorage,
    ) {}

    public function uploadAction(): ResponseInterface
    {
        $uploadedFile = $this->request->getUploadedFiles()['invoice'] ?? null;
        if ($uploadedFile === null) {
            return (new \TYPO3\CMS\Core\Http\Response())->withStatus(400);
        }

        $stored = $this->databaseFileStorage->store($uploadedFile);

        return $this->redirect('show', null, null, ['file' => $stored->uid]);
    }

    public function downloadAction(int $file): ResponseInterface
    {
        return $this->databaseFileStorage->createResponse($file, forceDownload: true);
    }
}
```

Example: storing generated bytes
--------------------------------

[](#example-storing-generated-bytes)

```
$stored = $this->databaseFileStorage->storeContents(
    filename: 'invoice-' . $invoice->getNumber() . '.pdf',
    contents: $pdfBytes,
    mimeType: 'application/pdf',
);
$invoice->setFileUid($stored->uid);
```

Example: MM relation to a domain model
--------------------------------------

[](#example-mm-relation-to-a-domain-model)

Add a `group` + `MM` column in your consumer TCA:

```
'attachments' => [
    'label' => 'Attachments',
    'config' => [
        'type' => 'group',
        'relationship' => 'manyToMany',
        'allowed' => 'tx_dbfilestorage_domain_model_file',
        'foreign_table' => 'tx_dbfilestorage_domain_model_file',
        'MM' => 'tx_myext_task_file_mm',
    ],
],
```

The MM table is auto-created by TYPO3's schema analyzer. On the Extbase side, use `StoredFileReference` in your model:

```
use B13\DbFileStorage\Domain\Model\StoredFileReference;
use B13\DbFileStorage\Domain\Repository\StoredFileReferenceRepository;

// In your model:
/** @param ObjectStorage $attachments */
protected ObjectStorage $attachments;

// In your upload action:
$stored = $this->databaseFileStorage->store($uploadedFile);
$ref = $this->storedFileReferenceRepository->findByUid($stored->uid);
$task->addAttachment($ref);
$this->taskRepository->update($task);
```

Extbase writes the MM rows automatically on `persistAll()`.

> **Note on cascade delete:** Extbase's `#[Cascade('remove')]` only works for 1:n relations (`HAS_MANY`), not for M:N (`HAS_AND_BELONGS_TO_MANY`). To soft-delete the file row when removing a relation, call `$databaseFileStorage->delete($ref->getUid())` explicitly in your controller.

Schema
------

[](#schema)

One table — [ext\_tables.sql](ext_tables.sql):

ColumnTypeNotes`uid`int auto\_incrementPrimary key`deleted`tinyintSoft-delete flag`filename`varchar(255)Original client filename`mime_type`varchar(127)Detected via `finfo` / TYPO3 map`size`bigintSize in bytes`sha1`varchar(40)Content hash, indexed`content`longblobThe file bytes`crdate`intCreation timestampMariaDB/MySQL's `max_allowed_packet` governs the maximum insert size.

Development
-----------

[](#development)

```
ddev start          # installs TYPO3, creates tables, ready to use
```

- Backend:  — `admin` / `Password.1`
- Tests: ```
    ddev exec --dir /var/www/html/Build \
      typo3DatabaseDriver=pdo_sqlite \
      vendor/bin/phpunit -c ../Build/phpunit/FunctionalTests.xml
    ```

Credits
-------

[](#credits)

This extension was created by Jochen Roth in 2025 for [b13 GmbH, Stuttgart](https://b13.com).

###  Health Score

20

—

LowBetter than 13% of packages

Maintenance56

Moderate activity, may be stable

Popularity4

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity12

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/e63334d6fdbd8f65eeb10a8e8913dca96a07ec2caffebdb2381c7b573c128237?d=identicon)[ochorocho](/maintainers/ochorocho)

---

Top Contributors

[![ochorocho](https://avatars.githubusercontent.com/u/4623070?v=4)](https://github.com/ochorocho "ochorocho (3 commits)")

### Embed Badge

![Health badge](/badges/b13-db-file-storage/health.svg)

```
[![Health](https://phpackages.com/badges/b13-db-file-storage/health.svg)](https://phpackages.com/packages/b13-db-file-storage)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
