Skip to main content

Overview

LaraCMS integrates Spatie Media Library, a powerful package for associating files with Eloquent models. It provides automatic image conversions, responsive images, custom properties, and organized media management.

What is Spatie Media Library?

Spatie Media Library is a Laravel package that:
  • Associates media files with database models
  • Generates multiple image sizes automatically
  • Organizes files by model and collection
  • Tracks file metadata (size, mime type, custom properties)
  • Provides responsive image support
  • Handles file deletion with model cleanup
Official Documentation: https://spatie.be/docs/laravel-medialibrary

Database Structure

Spatie creates a media table to store file information:
media
  - id: bigint (primary key)
  - model_type: string (polymorphic type)
  - model_id: bigint (polymorphic id)
  - uuid: uuid (unique identifier)
  - collection_name: string
  - name: string (original filename)
  - file_name: string (stored filename)
  - mime_type: string
  - disk: string (storage disk)
  - conversions_disk: string
  - size: bigint (bytes)
  - manipulations: json
  - custom_properties: json
  - generated_conversions: json
  - responsive_images: json
  - order_column: integer
  - created_at: timestamp
  - updated_at: timestamp
Reference: database/migrations/2025_03_30_145318_create_media_table.php

Implementation in LaraCMS

Models Using Media Library

LaraCMS implements Spatie Media Library in two primary models:
Albums use media for cover images and album-wide media:
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Spatie\Image\Enums\Fit;
use Spatie\MediaLibrary\MediaCollections\Models\Media;

class Album extends Model implements HasMedia
{
    use InteractsWithMedia;

    // Define media conversions
    public function registerMediaConversions(?Media $media = null): void
    {
        $this->addMediaConversion('preview')
            ->fit(Fit::Contain, 300, 300)
            ->nonQueued();
    }
}
Features:
  • Cover photo for album preview
  • Automatic thumbnail generation (300x300)
  • Non-queued processing for immediate availability
Reference: app/Models/Album.php:42

Media Collections

Collections organize different types of media:
public function registerMediaCollections(): void
{
    // Photos collection - gallery images
    $this->addMediaCollection('images');
    
    // Example: Multiple collections per model
    $this->addMediaCollection('thumbnails')
        ->singleFile(); // Only one thumbnail allowed
    
    $this->addMediaCollection('documents')
        ->acceptsMimeTypes(['application/pdf']);
}
Benefits:
  • Separate image types (gallery, thumbnails, avatars)
  • Different processing rules per collection
  • Organized media retrieval

Image Conversions

Automatic Thumbnail Generation

Conversions create different image sizes automatically:
public function registerMediaConversions(?Media $media = null): void
{
    // Preview for gallery grids
    $this->addMediaConversion('preview')
        ->fit(Fit::Contain, 300, 300)
        ->nonQueued();
    
    // Additional conversions
    $this->addMediaConversion('thumb')
        ->fit(Fit::Crop, 100, 100)
        ->nonQueued();
    
    $this->addMediaConversion('large')
        ->fit(Fit::Max, 1920, 1080)
        ->optimize()
        ->queued(); // Process in background
}
Fit Options:
  • Fit::Contain - Scale to fit within dimensions (maintains aspect ratio)
  • Fit::Crop - Crop to exact dimensions
  • Fit::Max - Scale down if larger (never upscale)
  • Fit::Stretch - Ignore aspect ratio
Reference: app/Models/Photo.php:27

User Workflows

File Upload Process

  1. Admin Uploads Photo
    • Navigate to /admin/gallery/photos
    • Click “Upload New Photo”
    • Select image file from computer
  2. Spatie Processing
    • Original file stored in configured disk
    • UUID generated for unique identification
    • File metadata extracted (size, mime type)
    • Media record created in database
  3. Conversion Generation
    • Preview thumbnail created (300x300)
    • Additional conversions processed
    • Conversions stored alongside original
  4. Model Association
    • Media linked to Photo model via polymorphic relationship
    • Added to images collection
    • Available for immediate display
Code Example:
// Controller handling upload
$photo = Photo::create([
    'title' => $request->title,
    'description' => $request->description
]);

$photo->addMediaFromRequest('image')
    ->toMediaCollection('images');

Accessing Uploaded Files

Get First Media Item:
// Get URL of original image
$url = $photo->getFirstMediaUrl('images');

// Get URL of preview conversion
$previewUrl = $photo->getFirstMediaUrl('images', 'preview');
Get All Media in Collection:
$images = $photo->getMedia('images');

foreach ($images as $image) {
    echo $image->getUrl(); // Original
    echo $image->getUrl('preview'); // Conversion
}
Blade Template Usage:
{{-- Display original image --}}
<img src="{{ $photo->getFirstMediaUrl('images') }}" 
     alt="{{ $photo->title }}">

{{-- Display thumbnail --}}
<img src="{{ $photo->getFirstMediaUrl('images', 'preview') }}" 
     alt="{{ $photo->title }} thumbnail">

Media Cleanup

Delete Single Media Item:
$media = $photo->getFirstMedia('images');
$media->delete();
Clear Entire Collection:
$photo->clearMediaCollection('images');
Model Deletion: When a Photo is deleted, all associated media is automatically removed:
$photo->delete(); // Deletes photo AND all media files
Cascade Behavior:
  • Original files deleted from disk
  • All conversions deleted
  • Database records removed
  • No orphaned files

Advanced Features

Storing Metadata

Attach custom data to media:
$photo->addMedia($pathToFile)
    ->withCustomProperties([
        'photographer' => 'John Doe',
        'location' => 'Paris, France',
        'camera' => 'Canon EOS R5',
        'rating' => 5
    ])
    ->toMediaCollection('images');
Retrieve Properties:
$media = $photo->getFirstMedia('images');
$photographer = $media->getCustomProperty('photographer');
$allProperties = $media->custom_properties;
Use Cases:
  • Photo EXIF data
  • Copyright information
  • Alt text for accessibility
  • SEO metadata

Automatic Responsive Generation

Generate multiple image sizes for responsive design:
public function registerMediaConversions(?Media $media = null): void
{
    $this->addMediaConversion('responsive')
        ->withResponsiveImages()
        ->fit(Fit::Max, 1920, 1080);
}
Blade Usage:
{{ $photo->getFirstMedia('images')->img('responsive') }}
Generated HTML:
<img srcset="image-300w.jpg 300w,
             image-600w.jpg 600w,
             image-1200w.jpg 1200w"
     sizes="(max-width: 600px) 300px,
            (max-width: 1200px) 600px,
            1200px"
     src="image-1200w.jpg">

Restricting Upload Types

Control accepted file types:
public function registerMediaCollections(): void
{
    $this->addMediaCollection('images')
        ->acceptsMimeTypes(['image/jpeg', 'image/png', 'image/webp'])
        ->maxFilesize(5 * 1024 * 1024); // 5MB
}
Controller Validation:
$request->validate([
    'image' => 'required|image|mimes:jpeg,png,jpg,webp|max:5120'
]);

Storage Configuration

Media files are stored based on Laravel filesystem configuration:
// config/filesystems.php
'disks' => [
    'public' => [
        'driver' => 'local',
        'root' => storage_path('app/public'),
        'url' => env('APP_URL').'/storage',
        'visibility' => 'public',
    ],
],
Default Storage Path:
  • Original: storage/app/public/{model-id}/{filename}
  • Conversions: storage/app/public/{model-id}/conversions/{filename}
Public Access:
php artisan storage:link
Creates symlink: public/storagestorage/app/public

Performance Optimization

Queued Conversions

Process heavy images in background:
$this->addMediaConversion('large')
    ->fit(Fit::Max, 1920, 1080)
    ->optimize()
    ->queued(); // Background processing
Benefits:
  • Faster user response
  • Handles large images
  • Scalable processing

Eager Loading

Prevent N+1 queries:
// Load photos with media
$photos = Photo::with('media')->get();

// In controller
$photos = Photo::with('media')
    ->get();
Reference: app/Http/Controllers/Admin/PhotoController.php:17

Comparison: Before and After

Manual File Handling:
// Store file
$path = $request->file('image')->store('photos');

Photo::create([
    'title' => $request->title,
    'image_path' => $path
]);

// Manual thumbnail creation
$image = Image::make($request->file('image'));
$image->resize(300, 300);
$thumbPath = 'photos/thumbs/' . $filename;
$image->save(storage_path('app/public/' . $thumbPath));

// Manual cleanup on delete
Storage::delete($photo->image_path);
Storage::delete($photo->thumb_path);
Limitations:
  • Manual thumbnail creation
  • No automatic cleanup
  • Hard to organize multiple images
  • No metadata tracking

Common Operations

Update Media

// Replace existing media
$photo->clearMediaCollection('images');
$photo->addMedia($newFile)->toMediaCollection('images');

// Or use updateMedia (single file collection)
$photo->updateMedia($newFile, 'images');

Check Media Existence

// Check if media exists
if ($photo->hasMedia('images')) {
    $url = $photo->getFirstMediaUrl('images');
}

// Count media items
$count = $photo->getMedia('images')->count();

Download Media

// Force download
return $photo->getFirstMedia('images');

// Or use response
return response()->download(
    $photo->getFirstMedia('images')->getPath()
);

Best Practices

Collection Naming

Use descriptive collection names:
  • images - Main photos
  • thumbnails - Profile pictures
  • documents - PDFs, files
  • videos - Video content
Avoid generic names like files or media.

Conversion Strategy

Plan conversions carefully:
  • Thumbnails: Small, non-queued
  • Display: Medium, queued
  • Large: Queued with optimization
Don’t create unnecessary conversions.

Cleanup Policy

Configure automatic cleanup:
// Delete old unused media
Media::where('created_at', '<', now()->subDays(30))
    ->whereDoesntHave('model')
    ->delete();

CDN Integration

Serve media from CDN:
// config/medialibrary.php
'disk_name' => 's3',
'path_generator' => 'your-cdn-path',
Improves performance globally.

Troubleshooting

Issue: Thumbnails not appearingSolutions:
  1. Ensure storage is linked: php artisan storage:link
  2. Check queue is running: php artisan queue:work
  3. Verify image library installed (GD or Imagick)
  4. Check permissions on storage directory
  5. Run conversions manually: php artisan media-library:regenerate
Issue: Uploads timeout or failSolutions:
  1. Increase PHP upload limits in php.ini:
    upload_max_filesize = 20M
    post_max_size = 20M
    max_execution_time = 300
    
  2. Use queued conversions for large images
  3. Implement chunked uploads for very large files
  4. Consider client-side compression

Migration from Manual Storage

If converting existing file storage to Spatie:
// Migration script
Photo::all()->each(function ($photo) {
    if ($photo->image_path && Storage::exists($photo->image_path)) {
        $photo->addMedia(storage_path('app/public/' . $photo->image_path))
            ->preservingOriginal()
            ->toMediaCollection('images');
    }
});

Additional Resources

Build docs developers (and LLMs) love