This is FileHandling package to be used with FilamentPHP/Laravel.
rayzenai/file-manager is a Laravel package for this is filehandling package to be used with filamentphp/laravel..
It currently has 5 GitHub stars and 8.022 downloads on Packagist (latest version v5.2.2).
Install it with composer require rayzenai/file-manager.
Discover more Laravel packages by rayzenai
or browse all Laravel packages to compare alternatives.
Last updated
A comprehensive Laravel package for Filament v4 that provides advanced file management with automatic image resizing, compression, media metadata tracking, and seamless S3 integration. Built for high-performance applications handling large volumes of media content.
This package is developed and maintained by Kiran Timsina and RayzenTech.
Kiran Timsina is a full-stack developer specializing in Laravel and Filament applications. Connect on GitHub.
RayzenTech is a tech startup based in Nepal focused on creating smart business solutions. We specialize in automating complex processes and making them simple, from business automation to robotic process automation. Learn more at RayzenTech.
This package works with both AWS S3 and Cloudflare R2. Choose one of the following configurations:
.env file:# AWS S3 Configuration
FILESYSTEM_DISK=s3
AWS_ACCESS_KEY_ID=your-access-key-id
AWS_SECRET_ACCESS_KEY=your-secret-access-key
AWS_DEFAULT_REGION=us-east-1 # Your preferred region
AWS_BUCKET=your-bucket-name
AWS_URL=https://your-bucket.s3.amazonaws.com
# Or if using CloudFront:
# AWS_URL=https://your-cloudfront-distribution.cloudfront.net
# Optional CDN URL (defaults to AWS_URL)
CDN_URL=https://your-cdn-url.com
config/filesystems.php:'default' => env('FILESYSTEM_DISK', 's3'),
'disks' => [
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
'throw' => false,
],
],
config/livewire.php (if using Livewire uploads):'temporary_file_upload' => [
'disk' => 's3', // Use S3 for temporary uploads
'rules' => ['required', 'file', 'max:122880'],
'directory' => 'livewire-tmp',
'middleware' => null,
'preview_mimes' => [
'png', 'gif', 'bmp', 'svg', 'wav', 'mp4',
'mov', 'avi', 'wmv', 'mp3', 'm4a',
'jpg', 'jpeg', 'mpga', 'webp', 'wma',
],
'max_upload_time' => 5,
'cleanup' => true,
],
.env file:# Cloudflare R2 Configuration
FILESYSTEM_DISK=r2
CLOUDFLARE_R2_ACCESS_KEY_ID=your-r2-access-key
CLOUDFLARE_R2_SECRET_ACCESS_KEY=your-r2-secret-key
CLOUDFLARE_R2_BUCKET=your-bucket-name
CLOUDFLARE_R2_ENDPOINT=https://your-account-id.r2.cloudflarestorage.com
CLOUDFLARE_R2_URL=https://your-custom-domain.com
# Or use R2 public URL:
# CLOUDFLARE_R2_URL=https://pub-xxxxx.r2.dev
CLOUDFLARE_R2_USE_PATH_STYLE_ENDPOINT=false
# Optional CDN URL (defaults to CLOUDFLARE_R2_URL)
CDN_URL=https://your-custom-domain.com
config/filesystems.php:'default' => env('FILESYSTEM_DISK', 'r2'),
'disks' => [
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
'throw' => false,
],
'r2' => [
'driver' => 's3',
'key' => env('CLOUDFLARE_R2_ACCESS_KEY_ID'),
'secret' => env('CLOUDFLARE_R2_SECRET_ACCESS_KEY'),
'region' => 'auto', // Always use 'auto' for R2
'bucket' => env('CLOUDFLARE_R2_BUCKET'),
'url' => env('CLOUDFLARE_R2_URL'),
'visibility' => 'private',
'endpoint' => env('CLOUDFLARE_R2_ENDPOINT'),
'use_path_style_endpoint' => env('CLOUDFLARE_R2_USE_PATH_STYLE_ENDPOINT', false),
'throw' => false,
],
],
config/livewire.php (if using Livewire uploads):'temporary_file_upload' => [
'disk' => 'r2', // Use R2 for temporary uploads
'rules' => ['required', 'file', 'max:122880'],
'directory' => 'livewire-tmp',
'middleware' => null,
'preview_mimes' => [
'png', 'gif', 'bmp', 'svg', 'wav', 'mp4',
'mov', 'avi', 'wmv', 'mp3', 'm4a',
'jpg', 'jpeg', 'mpga', 'webp', 'wma',
],
'max_upload_time' => 5,
'cleanup' => true,
],
If you prefer to use the standard S3 configuration with R2, you can configure R2 using the AWS environment variables:
.env file:# Use S3 disk with R2 credentials
FILESYSTEM_DISK=s3
AWS_ACCESS_KEY_ID=your-r2-access-key
AWS_SECRET_ACCESS_KEY=your-r2-secret-key
AWS_DEFAULT_REGION=auto # Always 'auto' for R2
AWS_BUCKET=your-bucket-name
AWS_ENDPOINT=https://your-account-id.r2.cloudflarestorage.com
AWS_URL=https://your-custom-domain.com
AWS_USE_PATH_STYLE_ENDPOINT=false
CDN_URL=https://your-custom-domain.com
This approach uses the existing s3 disk configuration in filesystems.php without creating a separate r2 disk.
| Feature | AWS S3 | Cloudflare R2 | | --------------------- | --------------------------------- | ------------------------------------ | | Storage Cost | ~$0.023/GB | $0.015/GB | | Egress Fees | $0.09/GB after 1GB | $0 (free egress) | | API Compatibility | Native S3 API | S3-compatible API | | Region Config | Specific region (e.g., us-east-1) | Always use 'auto' | | Best For | AWS ecosystem integration | Cost-effective, high bandwidth usage |
Install via Composer:
composer require rayzenai/file-manager
Publish the configuration:
php artisan vendor:publish --tag="file-manager-config"
Publish and run migrations (for media metadata):
# Publish migration files to your app
php artisan vendor:publish --provider="Kirantimsina\FileManager\FileManagerServiceProvider" --tag="file-manager-migrations"
# Run the migrations
php artisan migrate
Configure storage and environment variables:
See the Storage Configuration section above for detailed setup instructions for AWS S3 or Cloudflare R2.
Additional configuration options for .env:
# Cache Control Settings (Optional)
FILE_MANAGER_CACHE_ENABLED=true
FILE_MANAGER_CACHE_MAX_AGE=31536000 # 1 year in seconds
FILE_MANAGER_CACHE_VISIBILITY=public # 'public' or 'private'
FILE_MANAGER_CACHE_IMMUTABLE=true # Add immutable directive
# Compression Settings (Optional)
FILE_MANAGER_COMPRESSION_ENABLED=true
FILE_MANAGER_COMPRESSION_QUALITY=85 # 1-100, quality level
FILE_MANAGER_COMPRESSION_FORMAT=webp # webp, jpg, png, avif
# Maximum image dimensions (images larger than this will be scaled down)
FILE_MANAGER_MAX_HEIGHT=2160 # Maximum height in pixels
FILE_MANAGER_MAX_WIDTH=3840 # Maximum width in pixels
# Compression Driver Settings
FILE_MANAGER_COMPRESSION_DRIVER=gd # 'gd' for built-in GD Library or 'imagick' for ImageMagick
Register the plugin in your Filament panel provider:
use Kirantimsina\FileManager\FileManagerPlugin;
public function panel(Panel $panel): Panel
{
return $panel
->plugins([
FileManagerPlugin::make(),
]);
}
The configuration file config/file-manager.php allows you to customize:
return [
// CDN URL for serving files
'cdn' => env('CDN_URL', env('AWS_URL', env('APP_URL'))),
// Maximum upload dimensions
'max-upload-height' => '5120', // pixels
'max-upload-width' => '5120', // pixels
// File upload size limits by type
'max-upload-size-image' => '8192', // KB - Max size for image uploads (8 MB)
'max-upload-size-video' => '102400', // KB - Max size for video uploads (100 MB)
'max-upload-size-document' => '20480', // KB - Max size for document uploads (20 MB)
// Model to directory mappings (supports full class names)
'model' => [
'App\Models\User' => 'users', // Or use User::class
'App\Models\Product' => 'products', // Or use Product::class
'App\Models\Blog' => 'blogs', // Or use Blog::class
// Add your models here
],
// Image sizes to generate
// Set to empty array [] to disable automatic resizing completely
'image_sizes' => [
'icon' => 64, // 64px height for small icons
'small' => 120, // 120px height for small thumbnails
'thumb' => 240, // 240px height for thumbnails
'card' => 360, // 360px height for card images
'medium' => 480, // 480px height for medium images
'large' => 720, // 720px height for large images
'full' => 1080, // 1080px height for full size
'ultra' => 2160, // 2160px height for ultra HD
],
// Default thumbnail size for MediaColumn components
'default_thumbnail_size' => env('FILE_MANAGER_DEFAULT_THUMBNAIL_SIZE', 'icon'),
'default_card_size' => env('FILE_MANAGER_DEFAULT_CARD_SIZE', 'card'),
// Cache Control Headers for Media Files
'cache' => [
'enabled' => env('FILE_MANAGER_CACHE_ENABLED', true),
// Cache duration in seconds
// Common values:
// 3600 = 1 hour
// 86400 = 1 day
// 604800 = 1 week
// 2592000 = 30 days
// 31536000 = 1 year (default)
'max_age' => env('FILE_MANAGER_CACHE_MAX_AGE', 31536000),
// Cache visibility:
// 'public' - Can be cached by browsers and CDNs
// 'private' - Only cached by browsers, not CDNs
'visibility' => env('FILE_MANAGER_CACHE_VISIBILITY', 'public'),
// Immutable directive - tells browsers the file will never change
// Recommended for versioned/hashed filenames
'immutable' => env('FILE_MANAGER_CACHE_IMMUTABLE', true),
],
// Compression settings
'compression' => [
'enabled' => env('FILE_MANAGER_COMPRESSION_ENABLED', true),
'driver' => env('FILE_MANAGER_COMPRESSION_DRIVER', 'gd'), // 'gd' or 'imagick'
'auto_compress' => env('FILE_MANAGER_AUTO_COMPRESS', true),
'quality' => env('FILE_MANAGER_COMPRESSION_QUALITY', 85), // 1-95
'format' => env('FILE_MANAGER_COMPRESSION_FORMAT', 'webp'), // webp, jpeg, jpg, png, avif
'mode' => env('FILE_MANAGER_COMPRESSION_MODE', 'contain'), // contain, crop, cover
'threshold' => env('FILE_MANAGER_COMPRESSION_THRESHOLD', 100 * 1024), // 100KB
// Maximum allowed dimensions (hard limits - images will never exceed these)
'max_height' => env('FILE_MANAGER_MAX_HEIGHT', 2160),
'max_width' => env('FILE_MANAGER_MAX_WIDTH', 3840),
],
// Media metadata tracking
'media_metadata' => [
'enabled' => env('FILE_MANAGER_METADATA_ENABLED', true),
'track_file_size' => env('FILE_MANAGER_TRACK_FILE_SIZE', true),
'track_dimensions' => env('FILE_MANAGER_TRACK_DIMENSIONS', true),
'track_mime_type' => env('FILE_MANAGER_TRACK_MIME_TYPE', true),
'model' => \Kirantimsina\FileManager\Models\MediaMetadata::class,
],
];
The HasMultimedia trait automatically handles media file processing including image resizing, video compression, and document management:
use Kirantimsina\FileManager\Traits\HasMultimedia;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
use HasMultimedia;
protected $fillable = [
'name',
'image', // Single image field
'gallery', // Multiple images field
'demo_video', // Video field
'manual_pdf', // Document field
];
protected $casts = [
'gallery' => 'array', // Cast array fields
];
/**
* Define which fields contain media files and their types
* Required by HasMultimedia trait for automatic processing
*/
public function mediaFieldsToWatch(): array
{
return [
'images' => ['image', 'gallery'],
'videos' => ['demo_video', 'promo_video'],
'documents' => ['manual_pdf', 'specifications'],
];
}
/**
* Optional: Define which field should be used for SEO title generation
* Remove this method if SEO titles are not needed for this model
*/
public function seoTitleField(): string
{
return 'name'; // or 'meta_title', 'seo_title', etc.
}
}
Key Features:
The package includes built-in SEO title functionality for better search engine optimization of media files:
use Kirantimsina\FileManager\Forms\Components\MediaUpload;
// Set SEO title directly
MediaUpload::make('image')
->seoTitle('Premium Chocolate Cake')
// Set SEO title from another field
MediaUpload::make('image')
->seoTitleFromField('name') // Uses the 'name' field value as SEO title
// Dynamic SEO title with closure
MediaUpload::make('image')
->seoTitle(fn (Get $get) => $get('meta_title') ?? $get('name'))
Configuration:
Control which models receive SEO titles in config/file-manager.php:
'seo' => [
'enabled_models' => [
'App\Models\Product',
'App\Models\Category',
'App\Models\Blog',
// Models that should have SEO titles
],
'excluded_models' => [
'App\Models\User',
'App\Models\Order',
// Models that should NOT have SEO titles
],
],
The MediaUpload component extends Filament's FileUpload with automatic compression and metadata tracking:
use Kirantimsina\FileManager\Forms\Components\MediaUpload;
// Example 1: Convert to WebP with custom quality
MediaUpload::make('image')
->label('Product Image')
->quality(90) // Compression quality, capped at 95
->toWebp() // Convert to WebP format
// Example 2: Compress but keep original format (JPEG stays JPEG, PNG stays PNG)
MediaUpload::make('photo')
->keepOriginalFormat() // Compress but maintain original format
->quality(85) // Still applies compression
// Example 3: Upload without ANY processing
MediaUpload::make('document')
->uploadOriginal() // Skip all processing - no compression, no conversion
// Example 4: Full featured upload
MediaUpload::make('product_image')
->toAvif() // Convert to AVIF format (requires Imagick)
->quality(90) // High quality
->trackMetadata() // Track file metadata
->multiple() // Allow multiple files
Available Methods:
Image Methods:
quality(int $quality): Set compression quality (1-95, default: from config)format(string $format): Set output format ('webp', 'jpeg', 'jpg', 'png', 'avif', 'original')toWebp(): Convert images to WebP formattoAvif(): Convert images to AVIF format (requires Imagick, not available with GD)keepOriginalFormat(): Compress but keep the original file formatuploadOriginal(): Skip ALL processing - no compression, no resizing, no format conversiontrackMetadata(): Enable/disable metadata trackingresize(): Enable Filament's built-in resizing (opposite of uploadOriginal)Video Methods:
toWebm(): Complete WebM setup with optimal settings (1000kbps, 1080p, CRF 30)toMp4(): Complete MP4 setup with optimal settings (1500kbps, 1080p, CRF 23)compressVideo(): Enable video compressionvideoFormat('webm'|'mp4'): Set video output formatvideoBitrate(int): Set video bitrate in kbpsvideoMaxDimensions(width, height): Set maximum video dimensionsvideoPreset('ultrafast'|'fast'|'medium'|'slow'|'veryslow'): Encoding speed/quality trade-offvideoCrf(int): Set quality (0-63, lower = better)videoAsync(bool): Enable async processing (recommended)Important Notes:
uploadOriginal() is called)toWebp(), toAvif(), or format() to override config formatuploadOriginal() to skip ALL processing and upload file exactly as-isFeatures:
Display images in tables with different interaction styles:
Display images with modal preview and optional editing capabilities:
use Kirantimsina\FileManager\Tables\Columns\MediaModalColumn;
// Basic usage with modal preview
MediaModalColumn::make('image_file_name')
->label('Product Image')
->thumbnailSize('small')
->modalSize('large')
->allowEdit() // Enable editing in modal
// With custom heading and relationship
MediaModalColumn::make('attachment_file_name')
->label('Attachments')
->relationship('attachments')
->heading('View Attachments')
->multiple() // Handle multiple images
->downloadable() // Allow downloads
->previewable() // Enable preview
// Legacy dot notation (still supported)
MediaModalColumn::make('product.image_file_name')
->label('Product Image')
->thumbnailSize('medium')
Display images that link to a dedicated media page:
use Kirantimsina\FileManager\Tables\Columns\MediaUrlColumn;
// Basic usage - links to media page
MediaUrlColumn::make('image_file_name')
->label('Product Image')
->thumbnailSize('small')
->openInNewTab() // Open link in new tab
// With relationship
MediaUrlColumn::make('featured_image')
->label('Featured Image')
->relationship('media')
->thumbnailSize('card')
Common Methods (both components):
thumbnailSize(): Set thumbnail size ('icon', 'small', 'medium', 'large', etc.)label(): Column labelrelationship(): Name of the Eloquent relationship (for HasMany, HasOne, BelongsTo)showMetadata(): Show file metadata in tooltipviewCountField(): Field to track view countsMediaModalColumn specific methods:
modalSize(): Size for modal preview imagesheading(): Modal heading (closure or string)allowEdit(): Enable editing images in modalmultiple(): Handle multiple imagesdownloadable(): Allow image downloadspreviewable(): Enable image previewuploadOriginal(): Upload file as-is without any processing (default: false)MediaUrlColumn specific methods:
openInNewTab(): Open media page in new tab (default: true)Features:
Relationship Support (v4.3+):
The relationship parameter allows you to display and manage images from related models:
// In your model
class CartItem extends Model
{
public function attachments(): HasMany
{
return $this->hasMany(AttachmentFile::class);
}
}
// In your Filament resource
MediaColumn::make(
field: 'attachment_file_name', // Field on the AttachmentFile model
relationship: 'attachments', // Relationship method name
showInModal: true
)
This will:
You can configure default sizes for MediaColumn components in your configuration:
// config/file-manager.php
'default_thumbnail_size' => 'thumb', // Default for thumbnail displays
'default_card_size' => 'large', // Default for card displays
Or via environment variables:
FILE_MANAGER_DEFAULT_THUMBNAIL_SIZE=thumb
FILE_MANAGER_DEFAULT_CARD_SIZE=large
Available size configurations:
default_thumbnail_size: Used for thumbnail displays in table columns (default: 'icon')default_card_size: Used for larger preview displays like modals (default: 'card')Individual columns can still override these defaults (limited to thumbnail size only):
MediaModalColumn::make('image')
->thumbnailSize('large') // This overrides the default
MediaUrlColumn::make('image')
->thumbnailSize('large') // This overrides the default
Display images in tables with modal preview:
use Kirantimsina\FileManager\Tables\Columns\S3Image;
S3Image::make('image')
->label('Image')
->size('medium') // Use specific size: 'small', 'medium', 'large'
->square() // Square aspect ratio
->circular() // Circular mask
The package includes a powerful Filament resource for comprehensive media management.




The MediaMetadata resource includes a dedicated Image Processor page - a powerful tool for testing and optimizing your image processing pipeline.

Upload & Process
Processing Options
Compression Drivers
Results & Analytics

use App\Models\Product;
use Kirantimsina\FileManager\Facades\FileManager;
// Upload a file
$result = FileManager::upload(
Product::class, // model class
$uploadedFile, // file
'summer-sale', // tag (optional)
false, // fit (optional, default: false)
true, // resize (optional, default: true)
false, // webp (optional, default: false)
false // reencode (optional, default: false)
);
$path = $result['file']; // Get the file path from result
// Upload multiple files
$result = FileManager::uploadImages(
Product::class, // model class
$uploadedFiles, // files array
'gallery', // tag (optional)
false, // fit (optional)
true // resize (optional)
);
$paths = $result['files']; // Get array of file paths
// Upload base64 encoded image
$result = FileManager::uploadBase64(
Product::class, // model class
$base64String, // base64 image
'user-upload', // tag (optional)
false, // fit (optional)
true // resize (optional)
);
$path = $result['file']; // Get the file path from result
// Move temp file without resizing
$path = FileManager::moveTempImageWithoutResize(
Product::class, // model class
'temp/abc123.jpg' // temp file path
);
// Move temp file with automatic resizing
$path = FileManager::moveTempImage(
Product::class, // model class
'temp/abc123.jpg' // temp file path
);
// Delete image and all its sizes
FileManager::deleteImage('products/image.jpg');
// Delete multiple images
FileManager::deleteImagesArray(['products/img1.jpg', 'products/img2.jpg']);
// Get SEO-friendly filename
$filename = FileManager::filename(
file: $uploadedFile,
tag: 'product-name',
extension: 'webp'
);
// Get image URL with specific size
$url = FileManager::getMediaPath('products/image.jpg', 'medium');
// Get CDN URL
$cdnUrl = FileManager::mainMediaUrl();
// Get upload directory for a model
$directory = FileManager::getUploadDirectory(Product::class);
// Get configured image sizes
$sizes = FileManager::getImageSizes();
use Kirantimsina\FileManager\Services\ImageCompressionService;
$service = new ImageCompressionService();
// Compress and save
$result = $service->compressAndSave(
sourcePath: '/tmp/upload.jpg',
destinationPath: 'products/compressed.webp',
quality: 85,
height: 1080,
width: null, // Auto-calculate
format: 'webp',
mode: 'contain',
disk: 's3'
);
// Compress existing S3 file
$result = $service->compressExisting(
filePath: 'products/large-image.jpg',
quality: 80
);
Compression Drivers:
driver: 'gd'): Built-in PHP image processing using GD extension. Fast, lower memory usage, suitable for most use cases. Supports WebP, JPEG, and PNG formats.driver: 'imagick'): Uses ImageMagick extension for image processing. Better quality, more features, supports additional formats like AVIF. Requires ImageMagick to be installed on the server.The package now includes comprehensive video compression capabilities using FFmpeg to convert and optimize video files.
Prerequisites:
# Install FFmpeg on your system
# macOS:
brew install ffmpeg
# Ubuntu/Debian:
sudo apt-get update
sudo apt-get install ffmpeg
# CentOS/RHEL:
sudo yum install ffmpeg
# Windows:
# Download from https://ffmpeg.org/download.html
Configuration:
# Video Compression Settings
FILE_MANAGER_VIDEO_COMPRESSION_ENABLED=true
FILE_MANAGER_VIDEO_COMPRESSION_METHOD=ffmpeg
FILE_MANAGER_VIDEO_COMPRESSION_FORMAT=webm # webm or mp4
FILE_MANAGER_VIDEO_CODEC=libvpx-vp9 # libvpx-vp9, libvpx, libx264, libx265
FILE_MANAGER_AUDIO_CODEC=libopus # libopus, libvorbis, aac
FILE_MANAGER_VIDEO_BITRATE=1000 # kbps
FILE_MANAGER_AUDIO_BITRATE=128 # kbps
FILE_MANAGER_VIDEO_MAX_WIDTH=1920
FILE_MANAGER_VIDEO_MAX_HEIGHT=1080
FILE_MANAGER_VIDEO_FRAME_RATE=30
FILE_MANAGER_VIDEO_PRESET=medium # ultrafast, fast, medium, slow, veryslow
FILE_MANAGER_VIDEO_CRF=30 # 0-63, lower = better quality
FILE_MANAGER_VIDEO_THREADS=4
FILE_MANAGER_VIDEO_TIMEOUT=3600 # seconds
FILE_MANAGER_VIDEO_THUMBNAIL=true
FILE_MANAGER_VIDEO_THUMBNAIL_TIME=1.0 # seconds into video
Using Video Compression in Forms:
use Kirantimsina\FileManager\Forms\Components\MediaUpload;
// Quick setup with optimal settings (recommended)
MediaUpload::make('promo_video')
->toWebm() // Complete WebM configuration with optimal settings
MediaUpload::make('demo_video')
->toMp4() // Complete MP4 configuration with optimal settings
// Manual configuration for custom requirements
MediaUpload::make('video')
->acceptedFileTypes(['video/mp4', 'video/webm', 'video/quicktime'])
->compressVideo() // Enable video compression
->videoFormat('webm') // Output format: webm or mp4
->videoBitrate(800) // Video bitrate in kbps
->videoMaxDimensions(1280, 720) // Max width and height
->videoPreset('fast') // Encoding preset
->videoCrf(28) // Quality (0-63, lower = better)
->videoAsync(true) // Process asynchronously (recommended)
Convenience Helper Methods:
The package provides two convenient helper methods that configure all video settings with a single call:
toWebm() - Optimized for web delivery with WebM format:
toMp4() - Universal compatibility with MP4 format:
Programmatic Video Compression:
use Kirantimsina\FileManager\Services\VideoCompressionService;
$service = new VideoCompressionService();
// Compress a video
$result = $service->compress(
video: '/path/to/video.mp4',
outputFormat: 'webm',
videoBitrate: 1000,
maxWidth: 1280,
maxHeight: 720,
preset: 'medium',
crf: 30
);
// Compress and save to S3
$result = $service->compressAndSave(
video: '/path/to/video.mp4',
outputPath: 'videos/compressed.webm',
outputFormat: 'webm',
videoBitrate: 800,
maxWidth: 1920,
maxHeight: 1080,
preset: 'fast',
crf: 28,
disk: 's3'
);
// Get video metadata without compression
$metadata = $service->getVideoMetadata('/path/to/video.mp4');
// Returns: width, height, duration, bitrate, size, format, codecs, frame_rate
Async Video Processing with Queue:
use Kirantimsina\FileManager\Jobs\CompressVideoJob;
// Dispatch video compression job
CompressVideoJob::dispatch(
videoPath: 'uploads/large-video.mp4',
outputPath: 'videos/optimized.webm',
outputFormat: 'webm',
videoBitrate: 1000,
maxWidth: 1920,
maxHeight: 1080,
preset: 'medium',
crf: 30,
disk: 's3',
modelClass: Product::class, // Optional: link to model
modelId: 123, // Optional: model ID
modelField: 'demo_video', // Optional: field name
replaceOriginal: true, // Replace original file
deleteOriginal: false // Delete original after compression
);
Bulk Video Compression Command:
# Compress all videos in media metadata
php artisan file-manager:compress-videos
# Compress videos with specific settings
php artisan file-manager:compress-videos \
--format=webm \
--bitrate=800 \
--max-width=1280 \
--max-height=720 \
--preset=fast \
--crf=28
# Compress videos from specific model/field
php artisan file-manager:compress-videos \
--model="App\Models\Product" \
--field=demo_video
# Compress videos in specific directory
php artisan file-manager:compress-videos \
--path=uploads/videos
# Options
--replace # Replace original files
--delete-original # Delete original after compression
--async # Use queue for processing
--dry-run # Preview without actual compression
--limit=10 # Process only 10 videos
Video Format Recommendations:
| Format | Video Codec | Audio Codec | Use Case | Pros | Cons | | -------- | ----------- | ----------- | ------------ | ------------------------------- | ----------------------------------- | | WebM | VP9 | Opus | Modern web | Better compression, open source | Limited browser support (no Safari) | | WebM | VP8 | Vorbis | Legacy web | Wider browser support | Larger files than VP9 | | MP4 | H.264 | AAC | Universal | Maximum compatibility | Larger files, licensing | | MP4 | H.265/HEVC | AAC | High quality | 50% smaller than H.264 | Limited browser support |
Preset Performance Guide:
CRF (Constant Rate Factor) Guidelines:
The package uses queued jobs for better performance. Jobs are dispatched to configurable queues.
Configure queue names in config/file-manager.php:
'queue' => [
// Queue name for image processing jobs (resizing, compression)
'images' => env('FILE_MANAGER_QUEUE_IMAGES', 'default'),
// Queue name for video processing jobs
'videos' => env('FILE_MANAGER_QUEUE_VIDEOS', 'default'),
// Queue name for file deletion jobs
'delete' => env('FILE_MANAGER_QUEUE_DELETE', 'default'),
],
If using Laravel Horizon (recommended for production): You might have to configure horizon to use supervisors as your requirements. Check horizon's docs for more info on how to configure queues.
If NOT using Horizon, you must explicitly specify the queues:
# Listen to both default and custom queues
php artisan queue:work --queue=default,images,videos
# Or in your composer.json dev script:
"php artisan queue:listen --queue=default,images,videos --tries=1"
β οΈ Important: If you're using a database queue driver without Horizon and don't specify the queue names, jobs on non-default queues will not be processed!
ResizeImages - Generate multiple sizes for uploaded images (uses queue.images)DeleteMedia - Clean up all media files and their sizes (uses queue.delete)PopulateMediaMetadataJob - Populate media metadata for existing media filesCompressVideoJob - Asynchronously compress and optimize video files (uses queue.videos)The package provides several powerful Artisan commands for managing your media files and metadata:
| Command | Purpose | Key Features |
| ----------------------------------- | --------------------------------------- | -------------------------------------------- |
| file-manager:manage-sizes | Add/remove image sizes for all media | Config checking, batch processing, dry run |
| file-manager:refresh-all | Queue refresh jobs for all media | Metadata sync, dimension updates, batch jobs |
| file-manager:populate-seo-titles | Generate SEO titles for media files | Dry run, model filtering, chunked processing |
| file-manager:update-seo-titles | Update SEO titles when models change | Model-specific updates, automatic tracking |
| file-manager:populate-metadata | Create metadata for existing images | Auto-fixes MIME types, progress tracking |
| file-manager:remove-duplicates | Remove duplicate metadata records | Safe cleanup, dry run preview |
| file-manager:update-cache-headers| Add cache headers to existing S3 images | Directory-specific, progress tracking |
| file-manager:cleanup-old-media | Delete old media files from storage | Age threshold, PrunableMedia trait, dry run |
Add or remove image sizes for all media files in the media_metadata table. This command is useful when you need to add a new size to your configuration or clean up unused sizes.
# Add a new size called 'xlarge' with 1440px height
php artisan file-manager:manage-sizes add xlarge 1440
# Preview what would be done without executing
php artisan file-manager:manage-sizes add xlarge 1440 --dry-run
# Skip confirmation prompts
php artisan file-manager:manage-sizes add xlarge 1440 --force
# Process in smaller chunks (useful for large datasets)
php artisan file-manager:manage-sizes add xlarge 1440 --chunk=50
# Remove the 'xlarge' size and delete all associated files
php artisan file-manager:manage-sizes remove xlarge
# Preview what would be deleted
php artisan file-manager:manage-sizes remove xlarge --dry-run
# Remove without confirmation
php artisan file-manager:manage-sizes remove xlarge --force
Configuration First Approach: The command enforces configuration updates before operations:
Two-Step Process:
config/file-manager.php'image_sizes' => [
'icon' => 64,
'small' => 120,
'xlarge' => 1440, // Add your new size here
],
Storage Requirements: Adding sizes will increase storage usage as new files are created
Processing Time: Large datasets may take considerable time to process
File Cleanup: Removing sizes permanently deletes the associated image files
Blocked when size not in config:
$ php artisan file-manager:manage-sizes add hero 1800
Size 'hero' is not found in your configuration.
Please add it to config/file-manager.php first:
'image_sizes' => [
// ... existing sizes ...
'hero' => 1800,
],
After updating your config, run this command again.
Success after adding to config:
$ php artisan file-manager:manage-sizes add hero 1800
Size 'hero' with height 1800px already exists in configuration
Add size 'hero' (1800px height) to 1,250 images? (yes/no) [no]:
> yes
1250/1250 [ββββββββββββββββββββββββββββββββββββββββ] 100%
Processing completed:
Total processed: 1250
Succeeded: 1248
Failed: 2
Successfully created 'hero' sized images for 1248 files
Queue refresh jobs to update metadata for all media files. This command checks file metadata, dimensions, and synchronizes database records with actual file states in S3 storage.
# Queue refresh jobs for all media metadata records
php artisan file-manager:refresh-all
# Preview what would be done without executing
php artisan file-manager:refresh-all --dry-run
# Skip confirmation prompts
php artisan file-manager:refresh-all --force
# Process in smaller chunks
php artisan file-manager:refresh-all --chunk=50
$ php artisan file-manager:refresh-all
Found 2,500 media metadata records to refresh
Queue refresh jobs for 2,500 media records? (yes/no) [no]:
> yes
Dispatching refresh jobs...
2500/2500 [ββββββββββββββββββββββββββββββββββββββββ] 100%
Successfully queued 2,500 refresh jobs
Batch ID: 9c8e1a5b-4d2f-4e8a-b1c3-9f7e2d6c8a4b
You'll receive notifications about progress and completion
Jobs will check each file for:
File size changes
MIME type changes
Image dimension changes
Parent model reference consistency
You can also use the "Refresh All (Queued)" bulk action in the MediaMetadata admin panel to refresh selected records through the UI.
Generate SEO-optimized titles for existing media files based on their parent model data:
# Populate SEO titles for all configured models
php artisan file-manager:populate-seo-titles
# Process with custom chunk size for large datasets
php artisan file-manager:populate-seo-titles --chunk=100
# Preview what would be processed (dry run)
php artisan file-manager:populate-seo-titles --dry-run
# Process specific model only
php artisan file-manager:populate-seo-titles --model=Product
# Overwrite existing SEO titles
php artisan file-manager:populate-seo-titles --overwrite
Features:
Update SEO titles when parent model data changes:
# Update all SEO titles for models that have changed
php artisan file-manager:update-seo-titles
# Update for a specific model type
php artisan file-manager:update-seo-titles --model=Product
# Update for a specific model instance
php artisan file-manager:update-seo-titles --model=Product --id=123
# Process with custom chunk size
php artisan file-manager:update-seo-titles --chunk=200
The HasMultimedia trait now includes automatic SEO title updates. Simply define which field to use for SEO titles in your model:
use Kirantimsina\FileManager\Traits\HasMultimedia;
class Product extends Model
{
use HasMultimedia;
/**
* Define which field should be used for SEO title generation
* This field is also monitored for changes to trigger updates
* Default: 'name'
*/
public function seoTitleField(): string
{
return 'meta_title'; // If meta_title is null/empty, SEO title will be null
}
}
The HasMultimedia trait now provides:
seoTitleField() changesHow SEO Titles Work:
seoTitleField() method will have SEO titlesImportant Notes:
seoTitleField() method won't have SEO titles (intentional)Example for different models:
// Product model - wants SEO titles from meta_title
class Product extends Model
{
use HasMultimedia;
public function mediaFieldsToWatch(): array
{
return [
'images' => ['image', 'gallery'],
'videos' => ['demo_video'],
'documents' => ['manual_pdf'],
];
}
public function seoTitleField(): string
{
return 'meta_title';
}
}
// Blog model - wants SEO titles from title field
class Blog extends Model
{
use HasMultimedia;
public function mediaFieldsToWatch(): array
{
return [
'images' => ['featured_image', 'cover_image'],
'videos' => [],
'documents' => [],
];
}
public function seoTitleField(): string
{
return 'title';
}
}
// Order model - no SEO titles needed (internal use)
class Order extends Model
{
use HasMultimedia;
public function mediaFieldsToWatch(): array
{
return [
'images' => ['receipt'],
'videos' => [],
'documents' => ['invoice_pdf'],
];
}
// No seoTitleField() method = no SEO titles for media
}
If you have existing images in your database before installing this package, you can populate their metadata:
# Populate metadata for all configured models
php artisan file-manager:populate-metadata
# Populate metadata for a specific model (supports both short and full class names)
php artisan file-manager:populate-metadata --model=Product
php artisan file-manager:populate-metadata --model="App\Models\Product"
# Populate metadata for a specific field
php artisan file-manager:populate-metadata --model=Product --field=image_file_name
# Process with custom chunk size (default is 1000)
php artisan file-manager:populate-metadata --chunk=500
# Process synchronously without queue (good for testing)
php artisan file-manager:populate-metadata --model=Product --sync --chunk=100
# Dry run to see what would be processed
php artisan file-manager:populate-metadata --dry-run
Improvements in the latest version:
This command will:
The package includes robust MIME type detection that:
populate-metadata commandfinfo functions for other file typesmime_content_type() as fallbackgetimagesize() for image validationThis ensures that files like .webp are correctly identified as image/webp and not incorrectly labeled as image/avif or binary/octet-stream.
Clean up duplicate media metadata records from your database:
# Preview duplicates without removing them (dry run)
php artisan file-manager:remove-duplicates --dry-run
# Remove duplicate media metadata records
php artisan file-manager:remove-duplicates
# Remove duplicates without confirmation prompt (non-interactive)
php artisan file-manager:remove-duplicates --force
# Process with custom chunk size
php artisan file-manager:remove-duplicates --chunk=500
# Combine options for automated scripts
php artisan file-manager:remove-duplicates --force --chunk=1000
What it does:
mediable_type + mediable_id + mediable_field + file_namecreated_at and id)Features:
--force flag for automationExample output:
π Analyzing media metadata for duplicates...
Found 15 groups of duplicates containing 45 total records.
Will remove 30 duplicate records (keeping the oldest in each group).
π Sample duplicates found:
+----------+----+-------+----------------------+-------+---------------------+
| Model | ID | Field | Filename | Count | Oldest Record |
+----------+----+-------+----------------------+-------+---------------------+
| Product | 123| image | product-image.webp | 4 | 2024-01-15 10:30:00 |
| User | 456| avatar| user-avatar.jpg | 3 | 2024-01-16 14:22:15 |
+----------+----+-------+----------------------+-------+---------------------+
This command is useful when:
Retroactively add cache control headers to existing images in S3:
# Update all images in all directories
php artisan file-manager:update-cache-headers
# Update images in a specific directory
php artisan file-manager:update-cache-headers products
php artisan file-manager:update-cache-headers users
# Preview changes without applying them (dry run)
php artisan file-manager:update-cache-headers --dry-run
# Limit the number of files to process (useful for testing)
php artisan file-manager:update-cache-headers --limit=100
# Show detailed output for each file processed
php artisan file-manager:update-cache-headers --detailed
# Combine options
php artisan file-manager:update-cache-headers products --dry-run --limit=10 --detailed
Features:
How it works:
copyObject API to update metadata without re-uploading filesThis command is essential if you have images uploaded before cache headers were implemented, ensuring all your images benefit from optimal browser and CDN caching.
π‘ Tip: Verifying Cache Headers
To check if cache headers are properly set on your images, use curl:
# Check headers for a specific image
curl -I https://your-cdn.amazonaws.com/products/image.jpg | grep -E "Cache-Control|Content-Type"
# Expected output:
# Cache-Control: public, max-age=31536000, immutable
# Content-Type: image/jpeg
// Get image URL with specific size
$url = getImagePath('products/image.jpg', 'medium');
// Get CDN URL
$url = getCdnUrl('products/image.jpg');
// Check if file is an image
$isImage = isImageFile('document.pdf'); // false
Define custom sizes in your config:
'image_sizes' => [
'thumbnail' => 150,
'card' => 400,
'hero' => 1920,
// Add your custom sizes
],
To completely disable automatic image resizing, set the image_sizes config to an empty array:
'image_sizes' => [],
This is useful when:
The HasMultimedia trait automatically determines the appropriate processing based on field type:
For complex data structures like checkout items:
// The trait handles nested arrays intelligently
$checkout->items = [
['product_id' => 1, 'images' => ['image1.jpg', 'image2.jpg']],
['product_id' => 2, 'images' => ['image3.jpg']],
];
php artisan queue:workconfig/file-manager.phpmoveTempImageWithoutResize() when the model has HasMultimedia traitAWS_ENDPOINT is set correctly (format: https://[account-id].r2.cloudflarestorage.com)AWS_DEFAULT_REGION=auto for R2[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"],
"AllowedOrigins": ["*"],
"ExposeHeaders": []
}
]
max_age to 31536000 (1 year) for versioned/hashed filenamespublic visibility for CDN cachingimmutable for static assets that never changeThe package automatically adds cache headers to all uploaded media files for optimal performance:
# Disable cache headers (not recommended)
FILE_MANAGER_CACHE_ENABLED=false
# Custom cache duration (in seconds)
FILE_MANAGER_CACHE_MAX_AGE=86400 # 1 day
FILE_MANAGER_CACHE_MAX_AGE=604800 # 1 week
FILE_MANAGER_CACHE_MAX_AGE=2592000 # 30 days
# Private caching (browser only, no CDN)
FILE_MANAGER_CACHE_VISIBILITY=private
# Allow revalidation (for frequently updated images)
FILE_MANAGER_CACHE_IMMUTABLE=false
All media files uploaded to S3 will automatically include these cache headers:
Cache-Control: public, max-age=31536000, immutable (default)Content-Type based on actual file formatThe package provides a cleanup command to delete old media files from storage based on age thresholds. This helps manage storage costs and keep your S3/R2 bucket clean.
PrunableMedia trait to models that have media filesprunableMediaPeriod() to specify how old files should be before deletionstorage_deleted_at timestamp is set on media_metadata records to track cleanup stateAdd the storage_deleted_at column to your media_metadata table by publishing and running the migration:
php artisan vendor:publish --tag="file-manager-migrations"
php artisan migrate
Add PrunableMedia trait to any model that has media files you want to clean up. The model must also use HasMultimedia:
use Kirantimsina\FileManager\Traits\HasMultimedia;
use Kirantimsina\FileManager\Traits\PrunableMedia;
class Product extends Model
{
use HasMultimedia, PrunableMedia;
/**
* Period in days after which media files should be deleted.
* Files older than this will be removed from storage.
*/
public static function prunableMediaPeriod(): int
{
return 365; // 1 year
}
/**
* Define which fields contain media files
*/
public function mediaFieldsToWatch(): array
{
return [
'images' => ['thumbnail', 'gallery'],
'documents' => ['datasheet'],
];
}
}
# Discover all models with PrunableMedia trait and clean their old media
php artisan file-manager:cleanup-old-media
# Preview what would be deleted without actually deleting
php artisan file-manager:cleanup-old-media --dry-run
# Target a specific disk (s3 or r2)
php artisan file-manager:cleanup-old-media --disk=r2
# Override age threshold for all models (ignores prunableMediaPeriod)
php artisan file-manager:cleanup-old-media --age=180
# Clean only a specific model
php artisan file-manager:cleanup-old-media --model="App\Models\Product"
The command:
created_at is older than the prunableMediaPeriod() thresholdmediaFieldsToWatch() fieldsFileManagerService::deleteImage() to delete each file β including all resized variantsstorage_deleted_at = now() on the corresponding media_metadata recordsThe command is idempotent β re-running it skips records that already have storage_deleted_at set.
Add to your app/Console/Kernel.php to run weekly:
$schedule->command('file-manager:cleanup-old-media')
->weekly()
->withoutOverlapping();
Or run manually via cron:
# Run every Sunday at 2 AM
0 2 * * 0 cd /path-to-your-project && php artisan file-manager:cleanup-old-media >> /dev/null 2>&1
You can also call the cleanup logic directly from PHP:
use Kirantimsina\FileManager\Facades\FileManager;
// Clean media older than 365 days for CartItem models
$result = FileManager::cleanupOldMedia(
ageInDays: 365,
mediableTypes: ['App\Models\CartItem'],
dryRun: false,
diskName: 's3'
);
// Result contains counts and messages
// $result['found'] - total records found
// $result['deleted'] - files actually deleted
// $result['errors'] - failed deletions
storage_deleted_at flagFileManagerService::deleteImage() which deletes original + all sizesprunableMediaPeriod()Please see CHANGELOG for recent changes.
Contributions are welcome! Please see CONTRIBUTING for details.
If you discover any security issues, please email [email protected] instead of using the issue tracker.
The MIT License (MIT). Please see License File for more information.
For support, please open an issue on GitHub.