Filament Media Library
Version 1.0.0-stable
Welcome to the Filament Media Library documentation. A clean, minimal, and production-ready Laravel package for managing model attachments, fully optimized for Laravel 13.x, Filament v5.x, and Livewire v4.x.
Architecture Focus
This library addresses core media pitfalls: maintaining permanent SEO-friendly asset links, compressing file size footprint via automatic WebP and responsive dimensions, detecting physical duplicates to save storage space, and providing developer-friendly integration via traits and dedicated Filament inputs.
Installation
Install the package inside your Laravel 13 project namespace using composer:
composer require tsrgtm/filament-media-library
Publish configuration defaults and database schema tables:
php artisan vendor:publish --provider="Tsrgtm\FilamentMediaLibrary\MediaLibraryServiceProvider"
Execute migrations to configure the database:
php artisan migrate
Detailed Configuration
The package preferences are defined in the published config file config/media-library.php. These configurations govern the storage adapters, sizing boundaries, conversions, and engine processing parameters.
Option Parameters & Value Limits
| Parameter | Data Type | Default Value | Value Options & Limits | Description |
|---|---|---|---|---|
disk |
string |
'public' |
'public', 's3', 'local', etc. |
The storage disk for public media files. Must correspond to a disk in config/filesystems.php. |
private_disk |
string |
'local' |
'local', 's3', etc. |
Storage disk for secure documents requiring signed temporary URLs. |
max_file_size |
integer |
52428800 (50MB) |
> 0 (in bytes) |
Strict maximum allowed size limit per uploaded file. Exceeding this triggers a validation error. |
webp_enabled |
boolean |
true |
true, false |
Enables or disables automatic conversion of image formats (jpeg, png, gif) to WebP. |
image_quality |
integer |
80 |
10 to 100 |
Compression quality parameter for conversions. 10 yields smallest footprint, 100 yields lossless quality. |
cache.enabled |
boolean |
true |
true, false |
Toggles query and URL route caching to optimize database queries. |
cache.ttl |
integer |
3600 |
>= 0 (seconds) |
Time-to-live duration for cache keys. 3600 equates to 1 hour. |
queue.enabled |
boolean |
false |
true, false |
If true, conversions are handled asynchronously in the background. If false, processed synchronously. |
duplicate_detection.enabled |
boolean |
true |
true, false |
Toggles SHA-256 hash checks on uploads to protect against duplicate files. |
duplicate_detection.strategy |
string |
'link' |
'link', 'separate' |
'link' references the same physical storage path. 'separate' writes files separately. |
Polymorphic Relations
To associate media collections with any Eloquent model, implement the HasMedia trait. Define your collections inside the mediaCollections() method.
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Tsrgtm\FilamentMediaLibrary\Traits\HasMedia;
class Post extends Model
{
use HasMedia;
public function mediaCollections(): array
{
return [
'featured_image' => [
'single' => true, // Evicts older files automatically on update
'conversions' => ['webp'],
'fallback' => '/images/default-featured.png'
],
'images' => [
'multiple' => true, // Allows attaching lists of files
],
];
}
}
Polymorphic Collection Configuration Options
Each key in the returned array represents a collection name (e.g. 'featured_image'), supporting these optional keys:
single(boolean): Iftrue, only a single media file is stored, and subsequent uploads automatically delete previous attachments in that collection. Iffalse(default), multiple files are permitted.conversions(array): List of custom conversions to execute on save (e.g.['thumb', 'medium']).fallback(string): Absolute public path/URL returned by `getFirstMediaUrl()` if no files are uploaded.
Fluent Upload API
Upload files from local paths or request payloads using the developer-friendly builder API:
// Upload files from request inputs
$post->addMedia($request->file('avatar'))
->withAltText('Profile avatar')
->toCollection('avatar');
// Upload local files
$post->addMedia(storage_path('temp/catalog.pdf'))
->preservingOriginal()
->usingName('Product Sheet')
->to('documents');
Image Processing & Responsive HTML
Using Intervention Image v3, uploaded photos are auto-optimized, resized, and converted to WebP. Additionally, the system generates responsive scaling variants (default: 480px, 800px, 1200px) and calculates dimensions.
Configuring Resizing Conversions
Define target image conversions inside the 'conversions' block of config/media-library.php:
'conversions' => [
'thumb' => [
'width' => 150, // Target width in pixels (integer)
'height' => 150, // Target height in pixels (integer)
'fit' => true, // Boolean: true crops (Cover), false scales keeping aspect ratio
],
'medium' => [
'width' => 800,
'height' => null,
'fit' => false,
]
]
Rendering Responsive Images in Blade
To print an image with calculated srcset attributes for screen dimensions, call the img() helper directly on a Media record:
// In your Blade file:
{!! $post->getFirstMedia('featured_image')->img('medium', ['class' => 'rounded-xl border']) !!}
This generates the following SEO-compliant HTML code:
<img src="/media-serve/14/photo.jpg"
alt="Profile avatar"
srcset="/media-serve/14/photo.jpg?w=480 480w, /media-serve/14/photo.jpg?w=800 800w"
sizes="(max-width: 1200px) 100vw, 1200px"
class="rounded-xl border" />
On-Demand Image Conversions & Format Mutation
From the Media Library page, you can dynamically trigger custom conversions on single or bulk image records without altering their persistent URLs:
- Convert Format: Convert format types dynamically between
JPEG,PNG, andWebP. - Quality Compression Slider: Tune the compression quality slider from
10(maximum optimization) to100(lossless). - Dimension Constraints: Scale down large original images on-the-fly by defining Max Width or Max Height constraint thresholds.
- Static URLs: When files are converted, the physical file is overwritten on disk. The serving URL remains unchanged, and the controller dynamically returns the mutated Content-Type from the database.
SEO Stable URLs
SEO Crucial Guideline
Changing directories or file formats directly on disk breaks links and harms search engine indexing. To prevent this, never serve raw file paths directly.
Assets are served via static URLs: /media-serve/{media_id}/{filename}. When requested, our controller checks headers (Accept WebP) and streams optimized data without modifying the URL address.
Livewire Integration
The library is fully compatible with Livewire's file upload subsystem. Files uploaded via Livewire represent instances of Livewire\Features\SupportFileUploads\TemporaryUploadedFile.
Since this class inherits from standard Laravel uploads, pass the Livewire variable directly to our Fluent upload builder inside your Livewire components:
namespace App\Livewire;
use Livewire\Component;
use Livewire\WithFileUploads;
use App\Models\Post;
class ManagePostMedia extends Component
{
use WithFileUploads;
public Post $post;
public $photo; // Holds Livewire TemporaryUploadedFile
public function save()
{
$this->validate([
'photo' => 'image|max:1024', // Standard validations
]);
// Process directly using our polymorphic Fluent API
$this->post->addMedia($this->photo)
->toCollection('gallery');
session()->flash('message', 'Media successfully uploaded.');
}
}
Filament Custom Component
A standard Filament FileUpload input expects to save file paths directly into the parent model's table column. Because this package utilizes a polymorphic database relationship, a standard input will fail.
To solve this, use our custom **MediaLibraryUpload** component. It handles dynamic relationship query loads (via loadStateFromRelationshipsUsing) and saves uploads securely into the correct database collections (via saveRelationshipsUsing) while keeping all standard FileUpload validation, drag-and-drop, and preview capabilities intact.
Usage in Filament Resources
Import and append the input component inside your Resource form scheme:
use Tsrgtm\FilamentMediaLibrary\Filament\Components\MediaLibraryUpload;
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('title')
->required(),
// Custom Polymorphic Media Library Uploader
MediaLibraryUpload::make('featured_image')
->label('Featured Image')
->collection('featured_image') // Links to HasMedia collection key
->image() // Validates image files
->maxSize(2048) // max size validation in KB
->imagePreviewHeight('150')
->required(),
MediaLibraryUpload::make('images')
->label('Gallery Images')
->collection('images')
->multiple() // Supports multi-upload layout
->reorderable(), // Drag & drop sorting
]);
}
WordPress-Style Media Library Picker
Instead of forcing users to upload files repeatedly, use the MediaLibraryPicker component. It launches a modal to search, browse, and reuse already-uploaded media records, preventing storage bloating.
When selecting a media asset, a sidebar is opened (matching WordPress's design) to let you inspect details (dimensions, size, file name) and edit metadata like Title and Alt Text in place.
use Tsrgtm\FilamentMediaLibrary\Filament\Components\MediaLibraryPicker;
public static function form(Form $form): Form
{
return $form
->schema([
// Select and reuse existing media items
MediaLibraryPicker::make('featured_image')
->label('Choose Featured Image')
->collection('featured_image')
->required(),
MediaLibraryPicker::make('gallery')
->label('Choose Gallery Images')
->collection('gallery')
->multiple(), // Supports selecting multiple items from library
]);
}
Smart Storage & Security
Duplicate File Links
To optimize server space, the system hashes each file payload (SHA-256). If a duplicate hash is detected on the disk, the uploader maps the database entry directly to the existing file path, omitting redundant disk writes.
Private Collections & Signed URLs
Files written to private disks (e.g. local, or private S3 buckets) are secured automatically. The getUrl() helper detects private visibility and generates temporary Signed URLs valid for 1 hour.
$privateUrl = $document->getFirstMediaUrl('invoices');
// Output: /media-serve/20/invoice.pdf?expires=178652672&signature=abcdef...
API Reference
| Method | Description |
|---|---|
addMedia($file) |
Starts fluent uploading. Supports local path, UploadedFile, or Livewire uploads. |
media($collection) |
Gets the collection (or first record in single mode). |
getFirstMediaUrl($collection, $conversion) |
Resolves the SEO URL path for the first item. |
img($conversion, $attributes) |
Compiles responsive HTML image tags. |