wotz/filament-media-library
A Filament media library plugin for browsing, uploading, and managing reusable images and files. Provides a central media manager, picker integration for forms, and a clean interface for organizing assets in Laravel admin panels.
Start by reading the package source rather than the README — it is a small src/ tree (a plugin class, a form component, a page/resource, and a couple of Livewire views) that you can read end to end in twenty minutes. v4.7.0 is a minor, non-breaking release on the 4.x line (two PRs since v4.6.1), so an existing ^4.6 constraint will pick it up; pin ^4.7 if you want the configurable attachment model and the reworked grid.
Minimal setup:
composer require wotz/filament-media-library:^4.7
php artisan vendor:publish --tag="filament-media-library-migrations" # confirm the tag in the service provider
php artisan vendor:publish --tag="filament-media-library-config"
php artisan migrate
php artisan storage:link
The package sits on top of spatie/laravel-medialibrary, so confirm that dependency in composer.json and that its migrations ran. If the media table is missing, nothing else will work and the failure surfaces as an opaque Livewire error inside the upload component.
After publishing the config in v4.7.0, read it top to bottom — the release adds an attachment model key, and that config file is now the first place to look when you need to swap in your own model (see tenancy below).
Register the plugin on the panel you actually use:
public function panel(Panel $panel): Panel
{
return $panel
->plugin(FilamentMediaLibraryPlugin::make());
}
Then make your first model media-aware and try the single-file case before anything else — a cover image on a Post:
class Post extends Model implements HasMedia
{
use InteractsWithMedia;
public function registerMediaCollections(): void
{
$this->addMediaCollection('cover')->singleFile();
}
}
FileUpload::make('cover') // or the package's own component, e.g. MediaLibraryFileUpload::make('cover')
->collection('cover')
->image()
->disk('public');
That is your first use case: one image, one collection, rendered in a table column and on the front end via $post->getFirstMediaUrl('cover'). Do not start with the media-library browser page, multi-select, or conversions — though note the browser page is a materially better starting point than it was before v4.7.0 (real breakpoints, card titles, thumbnail fallback), so it is no longer the thing to avoid on day two.
Resource form + media library picker. The typical selling point is a form component that opens a modal listing previously uploaded media, so editors reuse an image instead of re-uploading it. Wire it as a FileUpload-shaped field with a collection, and keep a second plain FileUpload field around while you evaluate the picker — comparing outputs side by side is the fastest way to understand what the package adds versus what Spatie already does.
Detach the upload widget from the storage schema. Treat the component as a UI concern. All conversions, responsive images, and registerMediaConversions() belong in the model, not the form. This keeps you portable: if the package stalls, you drop in Spatie's own SpatieMediaLibraryFileUpload and lose only the browser modal.
Central media page as an editorial surface. v4.7.0's grid work (breakpoints, card titles, thumbnail fallback) makes the library page genuinely usable as an "asset library" screen rather than a demo. Gate it with a policy; editors upload and tag there, developers never touch it. Set the media name on upload if you want the new card titles to say something meaningful — otherwise they fall back to filenames, which for camera exports is noise.
Rich editor integration. If you use Filament's RichEditor, check whether the package ships an attachment/insert-media action. If not, the standard pattern is registering a custom editor tool that calls $component->getMedia() on save and rewrites <img src> to media URLs — do it in a form saveRelationshipsUsing or a dehydrateStateUsing hook rather than in the page class.
Multiple collections, one model. Blog posts with cover + gallery, products with thumbnail + datasheet. Register each collection explicitly with singleFile() where it applies; without it, re-uploading appends instead of replaces and you get a silent "why are there four covers" bug.
Tenancy and ownership. In a multi-tenant panel, scope the library browser by tenant_id on the media table. As of v4.7.0 you should do this through the package's configurable attachment model — point the config at your own Media extends Spatie\MediaLibrary\Media with a global scope, rather than subclassing internals or overriding the page query. Confirm the exact config key from the published config file and wire it before writing a single tenant-aware page; that hook existing removes the main reason to fork this package.
Queue conversions. Set queue_conversions_by_default in config/media-library.php and run a worker in every environment, including local. Conversions generated synchronously in a Filament request will make the edit page feel broken on large images.
src in the network tab or inspect getFirstMediaUrl() directly; do not treat "an image appears" as proof the disk wiring is correct.storage:link and disk mismatch. Spatie's default disk is public; Filament's FileUpload defaults to the panel's default_filesystem_disk. Set ->disk() explicitly on the component and check config/media-library.php. Combined with the new fallback, this is now the most likely silent failure in the package.maxSize() is kilobytes, not megabytes. Everyone gets this wrong once.acceptedFileTypes must include the MIME, not just the extension for SVG and HEIC, and HEIC will still fail conversions without ImageMagick support. Disable image() validation if you only need to store and link the file.php artisan medialibrary:clean (or model:prune) rather than trusting cascade behaviour.singleFile() + registerMediaConversions() ordering. Conversions registered after the collection's singleFile() still apply, but conversions that reference a non-existent collection silently never run. Log $model->getMedia('collection')->count() when a thumbnail is blank.getFirstMediaUrl() per row is one query per row. Add ->with('media') to the resource's getEloquentQuery(). This is the single biggest performance trap with any Spatie-backed package.image-1.jpg, image-2.jpg. Provide a custom PathGenerator or FileNamer early; retrofitting renames across a live site is painful.Media model binding / PathGenerator override in config/media-library.php; (3) subclassing a form component with public methods (you cannot easily subclass a closure-heavy one). Only fork if the component hardcodes Collection or imports the Media model directly in a way the new config cannot intercept.vendor:publish --tag=filament-media-library-views, the v4.7.0 breakpoint/title/fallback markup will not appear — published views shadow the package's. Re-publish (or diff resources/views against the vendor copy) when upgrading, or you will wonder why the grid looks identical.@jyrkidn) and two focused PRs, which is a better signal than a silent repo. Still commit the lockfile and keep the fork escape hatch open. Diff composer.json against your Filament major version: this is a 4.x line, so it targets Filament v4 — a Filament v3 app needs the older branch or it fails at Panel::plugin() type-hint level with a confusing error.How can I help you explore Laravel packages today?