crumbls / filament-media-library
A WordPress-like media library for Filament with central media pool and polymorphic attachments.
Requires
- php: ^8.2
- filament/filament: ^5.0
- laravel/framework: ^12.0
- spatie/laravel-medialibrary: ^11.0
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.24
- orchestra/testbench: ^10.0
- pestphp/pest: ^4.0
README
A WordPress-style media library for Filament 5. Provides a central media pool with polymorphic attachments, so any model in your application can reference shared media through a clean pivot table.
Built on top of Spatie Media Library for file handling and image conversions.
Requirements
- PHP 8.2+
- Laravel 12+
- Filament 5+
- Spatie Laravel Media Library 11+
Installation
composer require crumbls/filament-media-library
The package auto-discovers its service provider. Migrations run automatically -- no publishing required.
Run the migrations:
php artisan migrate
Register the plugin in your Filament panel provider:
use Crumbls\FilamentMediaLibrary\FilamentMediaLibraryPlugin; public function panel(Panel $panel): Panel { return $panel // ... ->plugins([ FilamentMediaLibraryPlugin::make(), ]); }
Configuration
Publish the config file to customize settings:
php artisan vendor:publish --tag=filament-media-library-config
// config/filament-media-library.php return [ // Storage disk (any disk defined in config/filesystems.php) 'disk' => env('MEDIA_LIBRARY_DISK', 'public'), // Allowed upload types (glob-style MIME matching) 'accepted_file_types' => ['image/*', 'video/*', 'application/pdf'], // Max upload size in kilobytes 'max_file_size' => 10240, // Automatic image conversions 'image_conversions' => [ 'thumbnail' => ['width' => 150, 'height' => 150], 'medium' => ['width' => 300, 'height' => 300], 'large' => ['width' => 1024, 'height' => 1024], ], // Named collections (for future use) 'collections' => [], // Swap the Media model with your own 'models' => [ 'media' => \Crumbls\FilamentMediaLibrary\Models\Media::class, ], // Filament navigation 'filament' => [ 'navigation_group' => null, 'navigation_icon' => 'heroicon-o-photo', 'navigation_sort' => null, 'navigation_label' => 'Media Library', ], ];
Usage
Media Library Page
Once the plugin is registered, a "Media Library" page appears in your Filament panel navigation. It provides:
- Grid view of all uploaded media with thumbnails
- Drag-and-drop file uploads
- Search across title, alt text, caption, and description
- Type filtering (images, videos, documents)
- Detail modal with editable metadata (title, alt text, caption, description)
- Inline image editing (crop, rotate, flip) via CropperJS
- File URL display for easy copying
Attaching Media to Models
Add the HasMediaLibrary trait to any Eloquent model:
use Crumbls\FilamentMediaLibrary\Traits\HasMediaLibrary; class Post extends Model { use HasMediaLibrary; }
This gives you a full API for managing media attachments:
$post = Post::find(1); // Attach a single media item $post->attachMedia($mediaId); // Attach with a named collection $post->attachMedia($mediaId, 'featured-image'); // Attach with explicit order $post->attachMedia($mediaId, 'gallery', order: 3); // Attach multiple at once (order is auto-incremented) $post->attachMediaMany([$mediaId1, $mediaId2, $mediaId3]); // Attach multiple to a specific collection $post->attachMediaMany([$mediaId1, $mediaId2], 'gallery'); // Detach media $post->detachMedia($mediaId); // Detach from a specific collection only $post->detachMedia($mediaId, 'gallery'); // Sync a collection (replaces existing attachments) $post->syncMedia([$mediaId1, $mediaId2], 'gallery'); // Query media in a collection $galleryItems = $post->mediaInCollection('gallery')->get(); // Get all media (default collection) $media = $post->getMediaByCollection(); // Access the relationship directly $post->mediaLibrary; // MorphToMany, ordered by pivot 'order'
MediaPicker Form Component
Use the MediaPicker in any Filament form to let users select media from the library:
use Crumbls\FilamentMediaLibrary\Forms\Components\MediaPicker; public static function form(Schema $schema): Schema { return $schema->components([ MediaPicker::make('featured_image_id'), ]); }
Multiple selection:
MediaPicker::make('gallery_ids') ->multiple(),
Limit selections:
MediaPicker::make('gallery_ids') ->multiple() ->maxItems(5),
Named collection:
MediaPicker::make('hero_image_id') ->collection('hero'),
The picker opens a modal with search, pagination, and thumbnail previews. Selected media is stored as an ID (single) or array of IDs (multiple) on the model.
Database Column Requirements
The MediaPicker stores a media_library.id reference directly on your model's table. You must ensure:
- The column exists -- Add a nullable unsigned big integer column for the field name you pass to
MediaPicker::make(). - The column is fillable -- Add the column name to your model's
$fillablearray.
Example migration for adding an avatar column to a users table:
Schema::table('users', function (Blueprint $table) { $table->unsignedBigInteger('avatar')->nullable(); $table->foreign('avatar') ->references('id') ->on('media_library') ->nullOnDelete(); });
Then in your model:
protected $fillable = [ // ... 'avatar', ];
If you have an accessor on the same column (e.g., getAvatarAttribute()), remove it -- the MediaPicker needs the raw integer ID during form hydration. Use a separate method to resolve the media URL:
use Crumbls\FilamentMediaLibrary\Models\Media; public function getAvatarUrl(): ?string { if (! $this->avatar) { return null; } $media = Media::with('media')->find($this->avatar); return $media?->thumbnail_url; }
MediaColumn Table Column
Display media thumbnails in Filament tables:
use Crumbls\FilamentMediaLibrary\Tables\Columns\MediaColumn; public static function table(Table $table): Table { return $table->columns([ MediaColumn::make('media') ->collection('featured-image') ->size(50) ->circular(), ]); }
Options:
// Set thumbnail size in pixels (default: 40) MediaColumn::make('media')->size(60) // Circular thumbnail (default: false) MediaColumn::make('media')->circular() // Square thumbnail (default: true) MediaColumn::make('media')->square(false) // Specify collection (default: 'default') MediaColumn::make('media')->collection('gallery')
Performance tip: Eager-load the media relation to avoid N+1 queries:
public static function table(Table $table): Table { return $table ->modifyQueryUsing(fn ($query) => $query->with('mediaLibrary.media')) ->columns([ MediaColumn::make('media'), ]); }
Media Model
The Media model wraps Spatie Media Library and provides convenient accessors:
use Crumbls\FilamentMediaLibrary\Models\Media; $media = Media::find(1); $media->file_url; // Full URL to the original file $media->thumbnail_url; // Thumbnail conversion URL (falls back to original) $media->mime_type; // e.g. 'image/jpeg' $media->file_size; // Size in bytes $media->file_name; // Stored file name $media->isImage(); // true for image/* MIME types $media->isVideo(); // true for video/* MIME types $media->isPdf(); // true for application/pdf
Metadata fields:
| Field | Type | Max Length |
|---|---|---|
title |
string | 255 |
alt_text |
string | 255 |
caption |
text | 1000 |
description |
text | 5000 |
The model uses soft deletes. Each record gets an auto-generated UUID and tracks uploaded_by (foreign key to users).
Database Schema
The package creates two tables:
media_library -- Central media records
| Column | Type |
|---|---|
| id | bigint (PK) |
| uuid | string (unique) |
| title | string (nullable) |
| alt_text | string (nullable) |
| caption | text (nullable) |
| description | text (nullable) |
| disk | string (default: 'public') |
| uploaded_by | foreign key to users (nullable) |
| created_at | timestamp |
| updated_at | timestamp |
| deleted_at | timestamp (soft delete) |
mediables -- Polymorphic pivot table
| Column | Type |
|---|---|
| id | bigint (PK) |
| media_id | foreign key to media_library |
| mediable_type | string |
| mediable_id | bigint |
| collection | string (default: 'default') |
| order | unsigned int (default: 0) |
| created_at | timestamp |
| updated_at | timestamp |
Spatie Media Library also creates its own media table for the underlying file records.
Publishing Assets
# Config php artisan vendor:publish --tag=filament-media-library-config # Migrations (only if you need to customize them) php artisan vendor:publish --tag=filament-media-library-migrations # Views (for template customization) php artisan vendor:publish --tag=filament-media-library-views
Storage Disk
Set the storage disk via environment variable or config:
MEDIA_LIBRARY_DISK=s3
Any disk defined in config/filesystems.php works -- public, s3, r2, or a custom disk.
Extending the Media Model
Swap in your own Media model by updating the config:
// config/filament-media-library.php 'models' => [ 'media' => App\Models\CustomMedia::class, ],
Your custom model should extend the package's Media model:
namespace App\Models; use Crumbls\FilamentMediaLibrary\Models\Media; class CustomMedia extends Media { // Add custom methods, scopes, or relationships }
Security
Uploads are validated server-side:
- MIME type validation -- Files are checked against
accepted_file_typesusing glob-style matching (e.g.,image/*matchesimage/png). Rejected files are not stored. - File size limits -- Enforced against
max_file_sizeconfig value. - Filename sanitization -- Uploaded filenames are slugified to strip path traversal sequences, special characters, and double extensions.
- Metadata validation -- Title and alt text are limited to 255 characters, caption to 1000, description to 5000.
Rejected uploads dispatch an fml-uploads-rejected Livewire event with details for frontend notification.
Development
Code Style
This project uses Laravel Pint for code formatting. A pre-push git hook runs Pint automatically and blocks pushes with style violations.
Run Pint manually:
vendor/bin/pint src config database tests
Static Analysis
Larastan is configured at level 5:
vendor/bin/phpstan analyse --configuration=packages/filament-media-library/phpstan.neon
Roadmap
Add throttling to uploads. Add lifecycle events. Add tenant scoping.
Testing
The package uses Pest with 197 tests:
vendor/bin/pest --test-directory packages/filament-media-library/tests --configuration packages/filament-media-library/phpunit.xml
License
MIT. See LICENSE.