# Laravel Media Library
Laravel Media Library is a comprehensive file management package for Laravel applications that associates files with Eloquent models. It provides a powerful and intuitive API for handling file uploads, storing media on any Laravel filesystem disk (local, S3, etc.), and generating multiple image conversions. The package automatically manages file metadata, custom properties, and supports responsive images with lazy loading.
The library seamlessly integrates with Laravel's filesystem abstraction, enabling developers to organize media into collections, apply image manipulations through conversions, and serve files through customizable URL generators. It supports various media types including images, PDFs, videos, and SVGs, with automatic conversion and optimization capabilities. Media items are stored in a database table with polymorphic relationships, allowing any Eloquent model to have associated media files.
## Installation and Setup
Run migrations to create the media table
```bash
php artisan migrate
```
Publish configuration file
```bash
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="config"
```
Add HasMedia interface and InteractsWithMedia trait to your model
```php
use Illuminate\Database\Eloquent\Model;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
class Article extends Model implements HasMedia
{
use InteractsWithMedia;
// Optional: Register media collections
public function registerMediaCollections(): void
{
$this->addMediaCollection('featured')
->singleFile()
->acceptsMimeTypes(['image/jpeg', 'image/png']);
$this->addMediaCollection('gallery');
}
// Optional: Register conversions
public function registerMediaConversions(?Media $media = null): void
{
$this->addMediaConversion('thumb')
->width(100)
->height(100)
->sharpen(10);
$this->addMediaConversion('large')
->width(1200)
->height(800)
->performOnCollections('gallery');
}
}
```
## Adding Media from Files
Upload file from request
```php
use App\Models\Article;
$article = Article::find(1);
// From uploaded file
$article->addMedia($request->file('image'))
->toMediaCollection('featured');
// From file path
$article->addMedia('/path/to/image.jpg')
->toMediaCollection('gallery');
// With custom properties
$article->addMedia($request->file('document'))
->withCustomProperties(['author' => 'John Doe', 'category' => 'reports'])
->toMediaCollection('documents');
// Preserve original file
$article->copyMedia('/path/to/important.pdf')
->toMediaCollection('archives');
// Specify disk
$article->addMedia($request->file('video'))
->toMediaCollection('videos', 's3');
```
## Adding Media from URLs
Download and attach remote files
```php
// From URL with mime type validation
$article->addMediaFromUrl('https://example.com/photo.jpg', ['image/jpeg', 'image/png'])
->toMediaCollection('images');
// With custom name and filename
$article->addMediaFromUrl('https://example.com/document.pdf')
->usingName('Annual Report')
->usingFileName('report-2024.pdf')
->withCustomProperties(['year' => 2024])
->toMediaCollection('reports');
// From URL without validation
$article->addMediaFromUrl('https://example.com/file.zip')
->toMediaCollection('downloads');
```
## Adding Media from Base64
Handle base64 encoded files
```php
// From base64 string
$base64Image = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
$article->addMediaFromBase64($base64Image, ['image/png', 'image/jpeg'])
->usingFileName('encoded-image.png')
->toMediaCollection('images');
// From data URI
$dataUri = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
$article->addMediaFromBase64($dataUri)
->usingFileName('data-uri-image.png')
->toMediaCollection('images');
```
## Adding Media from Strings and Streams
Create files from text content or streams
```php
// From string
$article->addMediaFromString('This is the content of my text file.')
->usingFileName('notes.txt')
->usingName('Article Notes')
->toMediaCollection('documents');
// From stream
$stream = fopen('https://example.com/video.mp4', 'r');
$article->addMediaFromStream($stream)
->usingFileName('stream-video.mp4')
->toMediaCollection('videos');
```
## Adding Multiple Files from Request
Batch upload files from request
```php
// Multiple files by keys
$fileAdders = $article->addMultipleMediaFromRequest(['image1', 'image2', 'image3']);
foreach ($fileAdders as $fileAdder) {
$fileAdder->toMediaCollection('gallery');
}
// All files from request
$allFileAdders = $article->addAllMediaFromRequest();
foreach ($allFileAdders as $key => $fileAdder) {
$fileAdder->toMediaCollection('uploads');
}
// Single file from request by key
$article->addMediaFromRequest('featured_image')
->withCustomProperties(['featured' => true])
->toMediaCollection('images');
```
## Retrieving Media
Get media items from collections
```php
$article = Article::find(1);
// Get all media from collection
$mediaItems = $article->getMedia('gallery');
// Get first media
$firstImage = $article->getFirstMedia('images');
// Get last media
$lastImage = $article->getLastMedia('images');
// Check if model has media
if ($article->hasMedia('gallery')) {
$images = $article->getMedia('gallery');
}
// Get media with filters
$recentMedia = $article->getMedia('documents', ['created_at' => 'desc']);
$filteredMedia = $article->getMedia('images', function($media) {
return $media->getCustomProperty('featured') === true;
});
```
## Getting Media URLs
Retrieve URLs for media files
```php
$media = $article->getFirstMedia('images');
// Public URL
$url = $media->getUrl();
$fullUrl = $media->getFullUrl();
// URL for conversion
$thumbUrl = $media->getUrl('thumb');
$largeUrl = $media->getUrl('large');
// Temporary URL (for private S3 files)
$temporaryUrl = $media->getTemporaryUrl(now()->addMinutes(30));
$conversionTempUrl = $media->getTemporaryUrl(now()->addHours(1), 'thumb');
// First/Last media URL
$firstUrl = $article->getFirstMediaUrl('images');
$firstThumbUrl = $article->getFirstMediaUrl('images', 'thumb');
$lastUrl = $article->getLastMediaUrl('gallery');
```
## Getting Media Paths
Retrieve filesystem paths for media files
```php
$media = $article->getFirstMedia('documents');
// Full path
$path = $media->getPath();
// Path for conversion
$thumbPath = $media->getPath('thumb');
// Relative path
$relativePath = $media->getPathRelativeToRoot();
// First/Last media path
$firstPath = $article->getFirstMediaPath('images');
$lastPath = $article->getLastMediaPath('images', 'thumb');
```
## Working with Custom Properties
Store and retrieve metadata with media
```php
$media = $article->getFirstMedia('documents');
// Set custom property
$media->setCustomProperty('author', 'Jane Smith')
->setCustomProperty('department', 'Marketing')
->save();
// Get custom property
$author = $media->getCustomProperty('author');
$department = $media->getCustomProperty('department', 'Unknown');
// Check if property exists
if ($media->hasCustomProperty('author')) {
$author = $media->getCustomProperty('author');
}
// Forget property
$media->forgetCustomProperty('author')->save();
// Add with custom properties during upload
$article->addMedia($request->file('file'))
->withCustomProperties([
'author' => 'John Doe',
'tags' => ['important', 'review'],
'metadata' => ['version' => '1.0']
])
->toMediaCollection('files');
```
## Defining Media Collections
Configure collections with validation and behavior
```php
class Product extends Model implements HasMedia
{
use InteractsWithMedia;
public function registerMediaCollections(): void
{
// Single file collection
$this->addMediaCollection('thumbnail')
->singleFile()
->acceptsMimeTypes(['image/jpeg', 'image/png', 'image/webp']);
// Limited collection (keep only 5 latest)
$this->addMediaCollection('photos')
->onlyKeepLatest(5)
->withResponsiveImages();
// Collection with custom disk
$this->addMediaCollection('videos')
->useDisk('s3')
->storeConversionsOnDisk('public');
// Collection with file validation
$this->addMediaCollection('documents')
->acceptsFile(function ($file, $model) {
return $file->mimeType === 'application/pdf' && $file->size < 5000000;
})
->acceptsMimeTypes(['application/pdf']);
// Collection with fallback
$this->addMediaCollection('avatar')
->singleFile()
->useFallbackUrl('/images/default-avatar.png')
->useFallbackPath(public_path('images/default-avatar.png'));
}
}
```
## Defining Conversions
Create image manipulations and transformations
```php
use Spatie\MediaLibrary\MediaCollections\Models\Media;
class Article extends Model implements HasMedia
{
use InteractsWithMedia;
public function registerMediaConversions(?Media $media = null): void
{
// Basic resize
$this->addMediaConversion('thumb')
->width(150)
->height(150)
->sharpen(10);
// Crop and format
$this->addMediaConversion('square')
->crop(500, 500)
->format('webp')
->quality(90);
// Collection-specific conversion
$this->addMediaConversion('preview')
->width(800)
->height(600)
->performOnCollections('gallery', 'images')
->nonQueued();
// Keep original format
$this->addMediaConversion('large')
->width(1920)
->keepOriginalImageFormat();
// Conditional conversion
$this->addMediaConversion('watermarked')
->width(1200)
->watermark(public_path('watermark.png'))
->when($media?->collection_name === 'public-gallery');
// Video thumbnail
$this->addMediaConversion('video-thumb')
->extractVideoFrameAtSecond(5)
->width(640)
->performOnCollections('videos');
// Responsive images
$this->addMediaConversion('responsive')
->width(1200)
->withResponsiveImages();
// PDF preview
$this->addMediaConversion('pdf-preview')
->pdfPageNumber(1)
->width(600)
->performOnCollections('documents');
}
}
```
## Regenerating Conversions
Regenerate conversions after modifying definitions
```bash
# Regenerate all conversions
php artisan media-library:regenerate
# Regenerate for specific model
php artisan media-library:regenerate --model="App\Models\Article"
# Regenerate for specific IDs
php artisan media-library:regenerate --model="App\Models\Article" --ids=1,2,3
# Regenerate only specific conversions
php artisan media-library:regenerate --only=thumb,large
# Force regeneration
php artisan media-library:regenerate --force
```
## Working with Conversions
Check and manage generated conversions
```php
$media = $article->getFirstMedia('images');
// Check if conversion exists
if ($media->hasGeneratedConversion('thumb')) {
$thumbUrl = $media->getUrl('thumb');
}
// Get all conversion names
$conversions = $media->getMediaConversionNames();
// Returns: ['thumb', 'large', 'square']
// Mark conversion as generated
$media->markAsConversionGenerated('thumb');
// Mark as not generated
$media->markAsConversionNotGenerated('thumb');
// Get available URL from list of conversions
$url = $media->getAvailableUrl(['large', 'medium', 'thumb']);
// Returns URL of first available conversion
// Get all generated conversions
$generatedConversions = $media->getGeneratedConversions();
```
## Responsive Images
Generate and serve responsive image sets
```php
// Enable in collection
public function registerMediaCollections(): void
{
$this->addMediaCollection('images')
->withResponsiveImages();
}
// Enable in conversion
public function registerMediaConversions(?Media $media = null): void
{
$this->addMediaConversion('hero')
->width(1920)
->withResponsiveImages();
}
// Enable during upload
$article->addMedia($request->file('image'))
->withResponsiveImages()
->toMediaCollection('images');
// Get responsive image URLs
$media = $article->getFirstMedia('images');
$urls = $media->getResponsiveImageUrls();
// Get srcset attribute
$srcset = $media->getSrcset();
$conversionSrcset = $media->getSrcset('hero');
// Check if has responsive images
if ($media->hasResponsiveImages()) {
echo "
";
}
// In Blade template
```
## Streaming Media Responses
Stream media as HTTP responses
```php
use Illuminate\Support\Facades\Route;
// Direct download
Route::get('/media/{media}/download', function (Media $media) {
return $media;
});
// Inline display
Route::get('/media/{media}/view', function (Media $media) {
return $media->toInlineResponse(request());
});
// Custom chunk size for large files
Route::get('/media/{media}/stream', function (Media $media) {
return $media->setStreamChunkSize(2 * 1024 * 1024) // 2MB chunks
->toResponse(request());
});
// Stream in controller
class MediaController extends Controller
{
public function download(Media $media)
{
if (!auth()->user()->can('download', $media)) {
abort(403);
}
return $media->toResponse(request());
}
}
```
## Creating ZIP Archives
Download multiple media files as ZIP
```php
use Spatie\MediaLibrary\Support\MediaStream;
// Create ZIP from media collection
Route::get('/article/{article}/photos.zip', function (Article $article) {
$mediaItems = $article->getMedia('photos');
return MediaStream::create('photos.zip')->addMedia($mediaItems);
});
// Multiple collections
Route::get('/article/{article}/all-media.zip', function (Article $article) {
return MediaStream::create('article-media.zip')
->addMedia($article->getMedia('images'))
->addMedia($article->getMedia('documents'))
->addMedia($article->getMedia('videos'));
});
// Custom folder structure in ZIP
$article->getMedia('documents')->each(function ($media) {
$media->setCustomProperty('zip_filename_prefix', 'documents/');
$media->save();
});
$article->getMedia('images')->each(function ($media) {
$media->setCustomProperty('zip_filename_prefix', 'images/');
$media->save();
});
return MediaStream::create('organized.zip')
->addMedia($article->getMedia('documents'))
->addMedia($article->getMedia('images'));
// With custom ZIP options
return MediaStream::create('archive.zip')
->useZipOptions(function (&$options) {
$options['comment'] = 'Generated by Laravel Media Library';
})
->addMedia($mediaItems);
```
## Updating and Managing Media
Update media properties and manage collections
```php
// Update media metadata
$media = Media::find(1);
$media->name = 'Updated Name';
$media->setCustomProperty('status', 'reviewed');
$media->save();
// Update collection
$newMediaArray = [
['id' => 1, 'name' => 'First Image', 'custom_properties' => ['order' => 1]],
['id' => 2, 'name' => 'Second Image', 'custom_properties' => ['order' => 2]],
];
$article->updateMedia($newMediaArray, 'gallery');
// Clear collection
$article->clearMediaCollection('gallery');
// Clear except specific items
$keepMedia = $article->getFirstMedia('gallery');
$article->clearMediaCollectionExcept('gallery', $keepMedia);
// Delete specific media
$article->deleteMedia(1); // by ID
$article->deleteMedia($media); // by object
// Move media to another model
$product = Product::find(1);
$media = $article->getFirstMedia('images');
$newMedia = $media->move($product, 'photos');
// Copy media to another model
$copiedMedia = $media->copy($product, 'photos', 'public', 'custom-name.jpg');
// Copy with callback
$copiedMedia = $media->copy($product, 'photos', '', '', function ($fileAdder) {
return $fileAdder->withCustomProperties(['copied_at' => now()]);
});
```
## Deleting Media
Remove media files and database records
```php
// Delete media (removes file and database record)
$media = Media::find(1);
$media->delete();
// Delete model preserving media
$article = Article::find(1);
$article->deletePreservingMedia();
// Force delete with soft deletes
$article->forceDelete(); // deletes media automatically
// Clear all media before deleting model
$article->clearMediaCollection();
$article->delete();
```
## Rendering Media as HTML
Generate HTML img tags with attributes
```php
$media = $article->getFirstMedia('images');
// Basic img tag
echo $media->img();
// Output:
// With conversion
echo $media->img('thumb');
// Output:
// With custom attributes
echo $media->img('thumb', ['class' => 'img-fluid rounded', 'id' => 'hero-image']);
// Output:
// Using invoke
echo $media('large', ['loading' => 'lazy']);
// In Blade
{!! $media->img('thumb', ['class' => 'thumbnail']) !!}
// As Htmlable (automatic escaping)
{{ $media }} {{-- Renders as img tag --}}
```
## Email Attachments
Attach media to emails
```php
use Illuminate\Mail\Mailable;
class InvoiceMail extends Mailable
{
public function __construct(public Media $invoice)
{
}
public function build()
{
return $this->view('emails.invoice')
->attach($this->invoice->toMailAttachment());
}
}
// With conversion
$mail->attach($media->mailAttachment('pdf-preview'));
// Multiple attachments
class ReportMail extends Mailable
{
public function __construct(public Article $article)
{
}
public function build()
{
$mail = $this->view('emails.report');
foreach ($this->article->getMedia('documents') as $media) {
$mail->attach($media->toMailAttachment());
}
return $mail;
}
}
```
## Custom Path Generators
Customize file storage paths
```php
use Spatie\MediaLibrary\Support\PathGenerator\PathGenerator;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
class CustomPathGenerator implements PathGenerator
{
public function getPath(Media $media): string
{
return $media->model_type . '/' . $media->model_id . '/';
}
public function getPathForConversions(Media $media): string
{
return $this->getPath($media) . 'conversions/';
}
public function getPathForResponsiveImages(Media $media): string
{
return $this->getPath($media) . 'responsive/';
}
}
// Register in config/media-library.php
'path_generator' => CustomPathGenerator::class,
// Or per model
'custom_path_generators' => [
App\Models\Article::class => CustomPathGenerator::class,
],
```
## Custom URL Generators
Customize URL generation
```php
use Spatie\MediaLibrary\Support\UrlGenerator\BaseUrlGenerator;
class CustomUrlGenerator extends BaseUrlGenerator
{
public function getUrl(): string
{
$path = $this->getPathRelativeToRoot();
// Custom CDN URL
return 'https://cdn.example.com/' . $path;
}
}
// Register in config/media-library.php
'url_generator' => CustomUrlGenerator::class,
```
## Custom File Namers
Customize generated file names
```php
use Spatie\MediaLibrary\Support\FileNamer\FileNamer;
use Spatie\MediaLibrary\Conversions\Conversion;
class CustomFileNamer extends FileNamer
{
public function originalFileName(string $fileName): string
{
// Add timestamp to filename
$name = pathinfo($fileName, PATHINFO_FILENAME);
return $name . '-' . time();
}
public function conversionFileName(string $fileName, Conversion $conversion): string
{
$name = pathinfo($fileName, PATHINFO_FILENAME);
return $name . '-' . $conversion->getName();
}
public function responsiveFileName(string $fileName): string
{
return pathinfo($fileName, PATHINFO_FILENAME);
}
}
// Register in config/media-library.php
'file_namer' => CustomFileNamer::class,
```
## Queuing Conversions
Control conversion job execution
```php
// Queue all conversions by default (config)
'queue_conversions_by_default' => true,
// Non-queued conversion
$this->addMediaConversion('thumb')
->width(150)
->nonQueued();
// Queued conversion
$this->addMediaConversion('large')
->width(2000)
->queued();
// Custom queue during upload
$article->addMedia($request->file('image'))
->onQueue('image-processing')
->toMediaCollection('images');
// Custom queue for conversions
public function registerMediaConversions(?Media $media = null): void
{
$this->addMediaConversion('large')
->width(1920)
->queued();
}
```
## Working with Media Types
Determine media type and properties
```php
$media = $article->getFirstMedia('files');
// Get media type
$type = $media->type; // 'image', 'pdf', 'video', 'audio', 'other'
// Get extension
$extension = $media->extension; // 'jpg', 'png', 'pdf', etc.
// Get mime type
$mimeType = $media->mime_type; // 'image/jpeg', 'application/pdf', etc.
// Get file size
$size = $media->size; // in bytes
$humanSize = $media->humanReadableSize; // '2.5 MB'
// Get disk driver
$driver = $media->getDiskDriverName(); // 'local', 's3', etc.
$conversionDriver = $media->getConversionsDiskDriverName();
// Type checking
$typeFromExtension = $media->getTypeFromExtension();
$typeFromMime = $media->getTypeFromMime();
```
## Advanced Filtering
Filter media collections programmatically
```php
// Filter by custom property
$featuredMedia = $article->getMedia('images', function ($media) {
return $media->getCustomProperty('featured') === true;
});
// Filter by mime type
$pdfDocuments = $article->getMedia('documents', function ($media) {
return $media->mime_type === 'application/pdf';
});
// Filter by size
$largeFiles = $article->getMedia('files', function ($media) {
return $media->size > 1000000; // larger than 1MB
});
// Filter by date
$recentMedia = $article->getMedia('images', function ($media) {
return $media->created_at->isAfter(now()->subDays(7));
});
// Complex filtering
$filteredMedia = $article->getMedia('gallery', function ($media) {
return $media->mime_type === 'image/jpeg'
&& $media->size < 5000000
&& $media->hasCustomProperty('approved')
&& $media->getCustomProperty('approved') === true;
});
```
## Configuration Examples
Common configuration patterns
```php
// config/media-library.php
return [
// Use S3 for media storage
'disk_name' => env('MEDIA_DISK', 's3'),
// Increase max file size to 50MB
'max_file_size' => 1024 * 1024 * 50,
// Use Redis queue for conversions
'queue_connection_name' => 'redis',
'queue_name' => 'media-conversions',
// Always queue conversions
'queue_conversions_by_default' => true,
// Custom path structure
'path_generator' => App\MediaLibrary\CustomPathGenerator::class,
// CDN URL generator
'url_generator' => App\MediaLibrary\CdnUrlGenerator::class,
// Optimize images aggressively
'image_optimizers' => [
Spatie\ImageOptimizer\Optimizers\Jpegoptim::class => [
'-m80',
'--strip-all',
'--all-progressive',
],
],
// Use Imagick for better quality
'image_driver' => 'imagick',
// Temporary URLs valid for 1 hour
'temporary_url_default_lifetime' => 60,
// Add cache headers for S3
'remote' => [
'extra_headers' => [
'CacheControl' => 'max-age=31536000, public',
'Expires' => gmdate('D, d M Y H:i:s', time() + 31536000) . ' GMT',
],
],
];
```
## Laravel Media Library provides a production-ready solution for managing media files in Laravel applications, handling everything from basic file uploads to advanced image processing with conversions and responsive images. The package's flexible architecture allows for extensive customization through path generators, URL generators, and file namers, while its integration with Laravel's queue system ensures optimal performance for resource-intensive operations.
The library excels at organizing media through collections, enabling developers to define validation rules, storage locations, and behavior for different types of files. With built-in support for multiple filesystems, automatic image optimization, and polymorphic relationships, it scales seamlessly from small projects to large applications with millions of media files. The comprehensive API covers common use cases like generating thumbnails, creating ZIP downloads, streaming responses, and attaching files to emails, making it the complete media management solution for modern Laravel applications.