crumbls / filament-media-library
A WordPress-like media library for Filament with central media pool and polymorphic attachments.
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/crumbls/filament-media-library
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.
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
Testing
The package uses Pest with 179 tests:
vendor/bin/pest --test-directory packages/filament-media-library/tests --configuration packages/filament-media-library/phpunit.xml
License
MIT. See LICENSE.