# Filament Curator Filament Curator is a comprehensive media picker and manager plugin for Filament Admin (Laravel). It provides a robust solution for uploading, organizing, and managing media files with support for image manipulation through the Glide library. The plugin integrates seamlessly with Filament forms, tables, and rich editors, allowing developers to easily attach media to their models. The plugin offers advanced features including image curations (custom sizes and focal points), responsive image srcsets, path generators for flexible file organization, multi-tenancy support, and Blade components for rendering optimized images. It supports various relationship types (BelongsTo, BelongsToMany, MorphMany) and includes built-in fallback handling for missing media. ## Installation Install the package via Composer and run the installation command. ```bash composer require awcodes/filament-curator php artisan curator:install ``` ## CuratorPlugin - Panel Registration Register the Curator plugin with your Filament Panel to enable the media resource and configure navigation settings. ```php use Awcodes\Curator\CuratorPlugin; use Filament\Panel; use Filament\Support\Icons\Heroicon; class AdminPanelProvider extends PanelProvider { public function panel(Panel $panel): Panel { return $panel ->plugins([ CuratorPlugin::make() ->label('Media') ->pluralLabel('Media') ->navigationIcon(Heroicon::OutlinedPhoto) ->navigationGroup('Content') ->navigationSort(3) ->showBadge(true) ->registerNavigation(true) ->curations(true) ->fileSwap(true), ]); } } ``` ## CuratorPicker - Form Field The CuratorPicker field enables media selection in Filament forms with support for single or multiple media, validation, and relationship binding. ```php use Awcodes\Curator\Components\Forms\CuratorPicker; use Filament\Support\Enums\Size; // Basic single media picker CuratorPicker::make('featured_image_id') ->label('Featured Image') ->buttonLabel('Select Image') ->color('primary') ->size(Size::Medium) ->constrained(true) ->acceptedFileTypes(['image/jpeg', 'image/png', 'image/webp']) ->maxSize(5120) ->directory('posts/images') ->disk('public') ->visibility('public'); // Multiple media picker with relationship CuratorPicker::make('gallery_images') ->multiple() ->maxItems(10) ->relationship('galleryImages', 'id') ->orderColumn('order') ->listDisplay(true) ->lazyLoad(true) ->defaultPanelSort('desc'); // With image resizing on upload CuratorPicker::make('thumbnail_id') ->imageResizeTargetWidth(800) ->imageResizeTargetHeight(600) ->imageCropAspectRatio('4:3') ->preserveFilenames(); ``` ## CuratorPicker - Model Relationships Define Eloquent relationships on your model to associate media with your records. ```php use Awcodes\Curator\Models\Media; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; class Post extends Model { protected $fillable = ['title', 'content', 'featured_image_id']; // Single media relationship (BelongsTo) public function featuredImage(): BelongsTo { return $this->belongsTo(Media::class, 'featured_image_id', 'id'); } // Multiple media relationship (BelongsToMany) public function galleryImages(): BelongsToMany { return $this->belongsToMany(Media::class, 'media_post', 'post_id', 'media_id') ->withPivot('order') ->orderBy('order'); } } ``` ## CuratorColumn - Table Display Display media thumbnails in Filament tables with support for single or stacked multiple images. ```php use Awcodes\Curator\Components\Tables\CuratorColumn; use Filament\Tables\Table; public static function table(Table $table): Table { return $table ->columns([ // Single image column CuratorColumn::make('featured_image') ->size(40) ->circular() ->resolution(100), // Multiple images with stacking CuratorColumn::make('gallery_images') ->ring(2) ->overlap(4) ->limit(3) ->size(32), ]) // Eager load relationships to prevent N+1 queries ->modifyQueryUsing(fn ($query) => $query->with(['featured_image', 'gallery_images'])); } ``` ## Media Model The Media model represents uploaded files with attributes for dimensions, metadata, and generated URLs. ```php use Awcodes\Curator\Models\Media; // Query media $media = Media::find(1); // Access attributes $media->name; // 'my-image' $media->ext; // 'jpg' $media->path; // 'uploads/2024/01/15/my-image.jpg' $media->disk; // 'public' $media->size; // 245760 (bytes) $media->width; // 1920 $media->height; // 1080 $media->type; // 'image/jpeg' $media->alt; // 'Alt text' $media->title; // 'Image title' $media->description; // 'Image description' $media->caption; // 'Image caption' $media->exif; // ['Make' => 'Canon', ...] (array) // Generated URL attributes $media->url; // Full public URL $media->full_path; // Absolute filesystem path $media->thumbnail_url; // Small thumbnail URL $media->medium_url; // Medium size URL $media->large_url; // Large size URL $media->pretty_name; // Display name (title or filename) $media->placeholder; // Base64 blurred placeholder // Check and get curations if ($media->hasCuration('thumbnail')) { $curation = $media->getCuration('thumbnail'); // ['key' => 'thumbnail', 'width' => 200, 'height' => 200, 'path' => '...'] } ``` ## Glider Blade Component Render optimized images with on-the-fly Glide transformations using the `` component. ```blade {{-- Basic usage with media ID --}} {{-- With Glide transformations --}} {{-- Responsive images with srcset --}} {{-- With fallback for missing media --}} {{-- Full transformation options --}} ``` ## Curation Blade Component Render pre-defined image curations (custom crops with focal points) using the `` component. ```blade {{-- Render a specific curation --}} {{-- Practical usage with fallback to glider --}} @php $preset = new \Awcodes\Curator\Curations\CurationPreset::make('Thumbnail') ->width(200) ->height(200); @endphp @if ($media->hasCuration('thumbnail')) @else @endif ``` ## CurationPreset - Define Reusable Image Sizes Create curation presets for consistent image sizes that can be applied manually or as quick actions in the media manager. ```php use Awcodes\Curator\Curations\CurationPreset; use Awcodes\Curator\Facades\Curation; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function register(): void { Curation::presets([ CurationPreset::make('Thumbnail') ->width(200) ->height(200) ->format('webp') ->quality(80), CurationPreset::make('Hero Banner') ->width(1920) ->height(600) ->format('jpg') ->quality(90), CurationPreset::make('Social Card') ->width(1200) ->height(630) ->format('png') ->quality(95), ]); } } ``` ## GliderFallback - Register Fallback Images Define fallback images for when media items are missing or not found. ```php use Awcodes\Curator\Glide\GliderFallback; use Awcodes\Curator\Facades\Glide; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function register(): void { Glide::registerGliderFallbacks([ GliderFallback::make('placeholder') ->source('defaults/placeholder.jpg') ->alt('Placeholder image') ->width(800) ->height(600) ->type('image/jpeg'), GliderFallback::make('avatar') ->source('defaults/avatar.png') ->alt('Default avatar') ->width(150) ->height(150) ->type('image/png'), ]); } } ``` ## GlideBuilder - Programmatic URL Generation Build Glide-processed image URLs programmatically with a fluent API. ```php use Awcodes\Curator\Glide\GlideBuilder; // Build a transformed image URL $url = GlideBuilder::make() ->width(800) ->height(600) ->fit('crop') ->quality(80) ->format('webp') ->toUrl('uploads/images/photo.jpg'); // With advanced transformations $url = GlideBuilder::make() ->width(1200) ->height(800) ->fit('contain') ->background('ffffff') ->blur(2) ->brightness(5) ->contrast(10) ->sharpen(15) ->filter('greyscale') ->watermarkPath('watermarks/logo.png') ->watermarkPosition('bottom-right') ->watermarkWidth(100) ->watermarkAlpha(75) ->toUrl($media->path); // Get as query string $queryString = GlideBuilder::make() ->width(400) ->height(300) ->toQueryString(); // Output: "w=400&h=300" // Get as array $params = GlideBuilder::make() ->width(400) ->height(300) ->format('webp') ->toArray(); // Output: ['w' => 400, 'h' => 300, 'fm' => 'webp'] ``` ## PathGenerator - Custom Upload Paths Implement custom path generators to organize uploaded files according to your needs. ```php use Awcodes\Curator\PathGenerators\Contracts\PathGenerator; use Awcodes\Curator\PathGenerators\DatePathGenerator; use Awcodes\Curator\PathGenerators\UserPathGenerator; use Awcodes\Curator\Components\Forms\CuratorPicker; // Use built-in DatePathGenerator (saves to: uploads/2024/01/15/) CuratorPicker::make('image') ->pathGenerator(DatePathGenerator::class); // Use built-in UserPathGenerator (saves to: uploads/user-123/) CuratorPicker::make('image') ->pathGenerator(UserPathGenerator::class); // Create custom path generator class CategoryPathGenerator implements PathGenerator { public function getPath(?string $baseDir = null): string { $category = request()->input('category', 'uncategorized'); return ($baseDir ? $baseDir . '/' : '') . 'categories/' . $category; } } // Use custom generator CuratorPicker::make('image') ->pathGenerator(CategoryPathGenerator::class); ``` ## RichEditor Integration Enable media insertion in Filament's RichEditor using the AttachCuratorMediaPlugin. ```php use Awcodes\Curator\Components\Forms\RichEditor\AttachCuratorMediaPlugin; use Filament\Forms\Components\RichEditor; RichEditor::make('content') ->columnSpanFull() ->tools([ 'bold', 'italic', 'link', 'attachCuratorMedia', // Add the Curator media button ]) ->plugins([ AttachCuratorMediaPlugin::make(), ]) ->fileAttachmentsDisk('public') ->fileAttachmentsDirectory('content-images') ->fileAttachmentsVisibility('public'); ``` ## Glide Server Configuration Customize the Glide server settings for image processing. ```php use Awcodes\Curator\Facades\Glide; use League\Glide\Responses\LaravelResponseFactory; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function register(): void { // Change the base URL path (default: 'curator') Glide::basePath('media'); // Custom Glide server configuration Glide::serverConfig([ 'driver' => 'imagick', // or 'gd' 'response' => new LaravelResponseFactory(app('request')), 'source' => storage_path('app'), 'source_path_prefix' => 'public', 'cache' => storage_path('app'), 'cache_path_prefix' => '.cache', 'max_image_size' => 2000 * 2000, ]); } } ``` ## Configuration File Publish and customize the configuration file to set global defaults. ```php // config/curator.php return [ // Supported curation output formats 'curation_formats' => ['jpg', 'jpeg', 'png', 'webp', 'gif'], // Default storage settings 'default_disk' => env('CURATOR_DEFAULT_DISK', 'public'), 'default_directory' => 'uploads', 'default_visibility' => 'public', // Feature toggles 'features' => [ 'curations' => true, 'file_swap' => true, 'directory_restriction' => false, 'preserve_file_names' => false, 'tenancy' => [ 'enabled' => false, 'relationship_name' => 'tenant', ], ], // Glide security token (generate with: php artisan curator:generate-token) 'glide_token' => env('CURATOR_GLIDE_TOKEN'), // Custom Media model 'model' => \App\Models\Media::class, // Default path generator 'path_generator' => \Awcodes\Curator\PathGenerators\DatePathGenerator::class, // Resource customization 'resource' => [ 'label' => 'Media', 'plural_label' => 'Media', 'default_layout' => 'grid', 'navigation' => [ 'group' => 'Content', 'icon' => 'heroicon-o-photo', 'sort' => 3, 'should_register' => true, 'should_show_badge' => true, ], ], ]; ``` ## Helper Functions Use global helper functions for common operations. ```php // Get the Curator manager instance $curator = curator(); // Get the Glide manager instance $glide = glide(); // Get the Curation manager instance $curation = curation(); // Get a GlideBuilder instance $builder = glide_builder(); // Check if a file extension is resizable $isResizable = is_media_resizable('jpg'); // true $isResizable = is_media_resizable('pdf'); // false // Get media items by IDs (maintains order) $mediaItems = get_media_items([1, 5, 3]); // Returns Collection ordered by: [media_id_1, media_id_5, media_id_3] // Pass a single Media model $items = get_media_items($mediaModel); // Pass an array of media data $items = get_media_items([ ['id' => 1, 'name' => 'image1'], ['id' => 2, 'name' => 'image2'], ]); ``` ## Custom Media Model Extend the base Media model to add custom functionality or relationships. ```php use Awcodes\Curator\Models\Media; class CustomMedia extends Media { protected $table = 'curator'; protected $appends = [ 'url', 'full_path', 'thumbnail_url', 'medium_url', 'large_url', 'pretty_name', 'formatted_size', // Custom appended attribute ]; // Add custom relationships public function uploadedBy(): BelongsTo { return $this->belongsTo(User::class, 'uploaded_by'); } // Add custom attributes public function getFormattedSizeAttribute(): string { $bytes = $this->size; $units = ['B', 'KB', 'MB', 'GB']; $index = 0; while ($bytes >= 1024 && $index < count($units) - 1) { $bytes /= 1024; $index++; } return round($bytes, 2) . ' ' . $units[$index]; } } // Register in config/curator.php 'model' => \App\Models\CustomMedia::class, ``` ## Summary Filament Curator serves as a complete media management solution for Filament applications, handling everything from file uploads and organization to image optimization and display. The main use cases include: building content management systems requiring media libraries, e-commerce platforms needing product image galleries, blog systems with featured images and inline media, and any Laravel application requiring sophisticated file management with image processing capabilities. Integration patterns typically involve registering the CuratorPlugin in your Filament Panel, adding CuratorPicker fields to forms with appropriate relationships, using CuratorColumn in tables for visual media display, and rendering optimized images in Blade views using the Glider or Curation components. The plugin's modular architecture allows customization at every level—from path generators and custom models to curation presets and Glide server configuration—making it adaptable to virtually any media management requirement.