PHPackages                             mark7651/filament-mediator - 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. [Image &amp; Media](/categories/media)
4. /
5. mark7651/filament-mediator

ActiveLibrary[Image &amp; Media](/categories/media)

mark7651/filament-mediator
==========================

A media library for Filament panels: a wall of files, a form field and an editor button.

v0.1.4(today)010↑2900%MITPHPPHP ^8.3CI passing

Since Aug 28Pushed todayCompare

[ Source](https://github.com/mark7651/filament-mediator)[ Packagist](https://packagist.org/packages/mark7651/filament-mediator)[ Docs](https://github.com/mark7651/filament-mediator)[ RSS](/packages/mark7651-filament-mediator/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (10)Versions (6)Used By (0)

Filament Mediator
=================

[](#filament-mediator)

A media library for Filament panels. One plugin registration brings three things:

- a section of the panel where files are dropped, searched, described and deleted,
- a form field that lets a record hold one file,
- a button in the rich editor that puts pictures into a text.

Files are kept in a table of their own and served straight off their disk. Pictures are redrawn on the way in (turned upright, brought down to a sane size, written as webp) unless the project asks for them to be kept as they arrived, and drawn again to the size they are looked at by Glide, behind signed addresses.

The package ships Ukrainian and English.

Requirements
------------

[](#requirements)

PHP 8.3, Laravel 13, Filament 5.7, Livewire 4.4. Thumbnails need one of the image drivers of Intervention (GD or Imagick).

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

[](#installation)

```
composer require mark7651/filament-mediator
php artisan migrate
```

The migration creates the table named in `mediator.table`, `media` by default.

Register the plugin on the panel that gets the library:

```
use Mediator\Filament\MediatorPlugin;

$panel->plugin(
    MediatorPlugin::make()
        ->navigationGroup(fn (): string => __('navigation.settings'))
        ->navigationSort(3),
);
```

The group is taken as a closure as well, because a group named in the language of the panel cannot be read while the panel is still being built. The icon is `heroicon-o-photo` unless `navigationIcon()` says otherwise.

That registration also hangs the styles of the library on the panel, so a project has nothing to add to its own build.

A file in a record
------------------

[](#a-file-in-a-record)

```
use Mediator\Filament\Forms\MediaField;

MediaField::make('cover_id'),
MediaField::make('icon_id')->takes(['image/svg+xml', 'image/png']),
```

The field keeps the number of the file in the column it is named after. Without `takes()` it opens the whole library; with it, the wall shows those kinds alone and refuses anything else handed to it, including a file dropped straight onto the open library.

A picture in a text
-------------------

[](#a-picture-in-a-text)

```
use Filament\Forms\Components\RichEditor;
use Mediator\Filament\Forms\MediaImagePlugin;

RichEditor::make('body')
    ->plugins([MediaImagePlugin::make()])
    ->toolbarButtons([['bold', 'italic'], ['image']]),
```

The plugin supplies the tool, the form decides whether it stands in the toolbar, so `image` has to be named in `toolbarButtons()`. Several pictures can be chosen at once and go into the text in the order they were picked.

A picture is written as `` and never with a `style`, because the size a picture is drawn at belongs to the site the text is shown on. Filament writes that style itself, so the package binds `Filament\Forms\Components\RichEditor\TipTapExtensions\ImageExtension` to an extension of its own that takes it back out and moves the size into the attributes. A project that wants the style back binds that name to the extension of Filament in a provider of its own.

Counting where a file stands
----------------------------

[](#counting-where-a-file-stands)

The library is opened to tidy up, and tidying up is deleting. A picture is chosen from inside the record that needs it, so nothing in the panel says where a file has ended up. The register is the answer: a project writes down the places it puts files in, and the library counts them before it warns. A project that writes nothing down is a project where nothing is counted; the library still deletes, the warning simply has less to say.

```
use Mediator\Models\Media;
use Mediator\Uses\Places;

app(Places::class)
    ->standsIn(Article::class, 'cover_id', withTrashed: true)
    ->standsIn(Practice::class, 'icon_id')
    ->counted(fn (Media $file): int => Page::standingInBlocks((int) $file->getKey()));
```

A record with two columns for files is registered twice, once per column. `counted()` is for a place no column leads to: a file standing inside the json of a page is found by reading the pages, not by following a relation.

The panel of an open file says where it stands, not only how many places that is, and so does the warning that stands between the file and the disk: an editor about to delete a picture reads the kind and the name of every record that will lose it, each of them a way to the record itself. A record found through `standsIn()` is named by its `title`, its `name` or, carrying neither, by its number, and it is linked to its page in the panel wherever the project has a resource for that model. A place written down with `counted()` is a number and nothing more, unless the project hands over the records as well:

```
app(Places::class)->counted(
    fn (Media $file): int => Page::standingIn($file)->count(),
    fn (Media $file): Collection => Page::standingIn($file)->get(),
);
```

The library is tidied by deleting, and deleting is safe only where nothing stands on the file, so the wall has a button that leaves in it the files no record stands on. That question is put about the whole library at once and is answered by the places rather than by the files: a place with a column of its own answers it by itself, and a place written down with `counted()` answers it where the project hands over the numbers of the files standing in it.

```
app(Places::class)->counted(
    fn (Media $file): int => Page::standingIn($file)->count(),
    fn (Media $file): Collection => Page::standingIn($file)->get(),
    anywhere: fn (): array => Page::everyFileStandingInOne(),
);
```

A place that says nothing here is left out of that answer, and the files standing in it are counted among nobody's. The register knows what the project told it and nothing else.

### A file standing in a text

[](#a-file-standing-in-a-text)

A picture put into a text by the editor is held there by its address, so no column leads to it and nothing counts it. A project says which of its texts can hold files, and the library takes care of the rest:

```
app(Places::class)
    ->standsInText(Article::class, 'body')
    ->standsInText(PageTranslation::class, ['content', 'aside'], stands: fn (PageTranslation $telling): Page => $telling->page);
```

The library hangs a reading on the model itself: at the moment the record is saved the text is read and the files found in it are written down in a table of pairs, `mediator.texts_table`. Counting a place then costs a key of an index however far the texts of the project have grown, and reading every text of the project to answer one question never happens.

A text lives in the telling of a page in one language while the record a person opens is the page itself, so `stands` says which record is named and counted; without it the record that was saved is the record that is named. A page told in two languages is one place and not two.

Both the picture and the link are read, because a text holds a file either way, and a file is matched by the name the library gave it. A record thrown into the trash holds the pictures of its text as firmly as one standing on the site; a record gone for good lets go of them.

A text changed past the model, by an import or by a statement written by hand, leaves the table saying what was true before. The way back is:

```
php artisan mediator:relink
```

The register is a singleton and is filled from the `boot()` of a provider of the project.

A model of your own
-------------------

[](#a-model-of-your-own)

A project that needs the record to know more than the library does puts its own model in the config and inherits the one of the package:

```
// config/mediator.php
'model' => App\Models\Media::class,
```

```
namespace App\Models;

use Mediator\Models\Media as LibraryMedia;

class Media extends LibraryMedia
{
    // relations, casts, whatever the project needs of it
}
```

The library never names the model itself, it asks `Mediator::model()` and `Mediator::query()`.

Who may do what
---------------

[](#who-may-do-what)

The package registers `Mediator\Policies\MediaPolicy` for the model, but only where the project has no policy of its own for it. Laravel finds `App\Policies\MediaPolicy` for `App\Models\Media` by itself, and where it does, the policy of the package stands aside. So a project that wants deletion kept to administrators writes that policy and registers nothing.

The shipped policy lets any signed in user do everything.

Configuration
-------------

[](#configuration)

```
php artisan vendor:publish --tag=mediator-config
php artisan vendor:publish --tag=mediator-translations
php artisan vendor:publish --tag=mediator-views
```

What the config decides:

KeyWhat it is`model`, `table`the record of a file and the table it stands on`texts_table`the table of the files standing inside texts`types`every kind of file the library holds, and the extension each is written with`step`how many cards stand on the wall when it opens and are added at a time`disk`, `directory`, `visibility`where a new file is written; files are laid out in folders by year and month`ceilings`how heavy a file may be, pictures held to a tenth of the rest`pictures`the longest side a photograph is brought down to, the quality it is written with, and the kinds redrawn on the way in`thumbnails`the path the drawn pictures are served under, the token that signs the addresses, the cache folder and the named sizesThe library takes the kinds named in `types` and no others: the kind is read out of the bytes of the file and the extension is written from that list, so a picture handed over as `page.html` cannot end up served as a page of the domain the panel is signed in to. A project adds the kinds it needs there and takes away the ones it wants kept out.

`pictures.redraw` names the kinds redrawn on the way in. A kind left out of it is written to the disk as it arrived, byte for byte, and an empty list is a library that keeps every original: a studio handing photographs back to the people they belong to wants exactly that, and pays for it with the weight.

A project carrying files over from another library names that library's table in `table` and moves nothing.

### The ceiling of Livewire

[](#the-ceiling-of-livewire)

Files arrive through Livewire, which has a ceiling of its own, and it is the lower of the two that decides. `mediator.ceilings.default` is a hundred megabytes, so the host project has to raise Livewire to match:

```
// config/livewire.php
'temporary_file_upload' => [
    'rules' => ['required', 'file', 'max:102400'],
],
```

The web server counts too: `upload_max_filesize` and `post_max_size` in PHP, `client_max_body_size`in nginx.

Thumbnails
----------

[](#thumbnails)

Pictures are redrawn by Glide through a route of this package, `mediator.thumbnail`. Every address carries a signature, so nobody outside can ask for a thousand sizes of one file and fill the disk with them. Set `MEDIATOR_THUMBNAIL_TOKEN` to sign them with a key of their own; without it the key of the application signs them.

The address of the file itself stays what it always was. A picture on a page is served straight off its disk and never through this route: the redrawn sizes are for the panel.

Testing
-------

[](#testing)

```
composer install
vendor/bin/pest
```

The bench is orchestra/testbench, which does not discover packages, so every provider the tests need is listed in `getPackageProviders()` of `tests/TestCase.php`. Livewire has to stand after Filament there.

License
-------

[](#license)

MIT.

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance100

Actively maintained with recent releases

Popularity7

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

 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.

###  Release Activity

Cadence

Every ~0 days

Total

5

Last Release

0d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/37314381?v=4)[Max](/maintainers/mark7651)[@mark7651](https://github.com/mark7651)

---

Top Contributors

[![mark7651](https://avatars.githubusercontent.com/u/37314381?v=4)](https://github.com/mark7651 "mark7651 (25 commits)")

---

Tags

laravelmedialivewireglidemedia libraryfilament

###  Code Quality

TestsPest

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/mark7651-filament-mediator/health.svg)

```
[![Health](https://phpackages.com/badges/mark7651-filament-mediator/health.svg)](https://phpackages.com/packages/mark7651-filament-mediator)
```

###  Alternatives

[tallstackui/tallstackui

TallStackUI is a powerful suite of Blade components that elevate your workflow of Livewire applications.

731189.9k16](/packages/tallstackui-tallstackui)[psalm/plugin-laravel

Psalm plugin for Laravel

3345.4M354](/packages/psalm-plugin-laravel)[awcodes/filament-curator

A media picker plugin for FilamentPHP.

440384.7k29](/packages/awcodes-filament-curator)[tomshaw/electricgrid

A feature-rich Livewire package designed for projects that require dynamic, interactive data tables.

119.8k](/packages/tomshaw-electricgrid)[rawilk/profile-filament-plugin

Profile &amp; MFA starter kit for filament.

3915.5k](/packages/rawilk-profile-filament-plugin)

PHPackages © 2026

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