Files
InvoiceShelf/.cursor/skills/medialibrary-development/references/medialibrary-guide.md
2026-03-21 18:53:33 +01:00

14 KiB

Laravel Media Library Reference

Complete reference for spatie/laravel-medialibrary. Full documentation: https://spatie.be/docs/laravel-medialibrary

Model Setup

Implement HasMedia and use InteractsWithMedia:

use Illuminate\Database\Eloquent\Model;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;

class BlogPost extends Model implements HasMedia
{
    use InteractsWithMedia;

    public function registerMediaCollections(): void
    {
        $this->addMediaCollection('images');
    }

    public function registerMediaConversions(?Media $media = null): void
    {
        $this->addMediaConversion('thumb')
            ->fit(Fit::Contain, 300, 300);
    }
}

Adding Media

From uploaded file

$model->addMedia($request->file('image'))->toMediaCollection('images');

From request (shorthand)

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

From URL

$model->addMediaFromUrl('https://example.com/image.jpg')->toMediaCollection('images');

From string content

$model->addMediaFromString('raw content')->usingFileName('file.txt')->toMediaCollection('files');

From base64

$model->addMediaFromBase64($base64Data)->usingFileName('photo.jpg')->toMediaCollection('images');

From stream

$model->addMediaFromStream($stream)->usingFileName('file.pdf')->toMediaCollection('files');

From existing disk

$model->addMediaFromDisk('path/to/file.jpg', 's3')->toMediaCollection('images');

Multiple files from request

$model->addMultipleMediaFromRequest(['images'])->each(function ($fileAdder) {
    $fileAdder->toMediaCollection('images');
});

$model->addAllMediaFromRequest()->each(function ($fileAdder) {
    $fileAdder->toMediaCollection('images');
});

Copy instead of move

$model->copyMedia($pathToFile)->toMediaCollection('images');
// or
$model->addMedia($pathToFile)->preservingOriginal()->toMediaCollection('images');

FileAdder Options

All methods are chainable before calling toMediaCollection():

$model->addMedia($file)
    ->usingName('Custom Name')              // display name
    ->usingFileName('custom-name.jpg')      // filename on disk
    ->setOrder(3)                           // order within collection
    ->withCustomProperties(['alt' => 'A landscape photo'])
    ->withManipulations(['thumb' => ['filter' => 'greyscale']])
    ->withResponsiveImages()                // generate responsive variants
    ->storingConversionsOnDisk('s3')         // put conversions on different disk
    ->addCustomHeaders(['CacheControl' => 'max-age=31536000'])
    ->toMediaCollection('images');

Store on cloud disk

$model->addMedia($file)->toMediaCollectionOnCloudDisk('images');

Media Collections

Define in registerMediaCollections():

public function registerMediaCollections(): void
{
    // Basic collection
    $this->addMediaCollection('images');

    // Single file (replacing previous on new upload)
    $this->addMediaCollection('avatar')
        ->singleFile();

    // Keep only latest N items
    $this->addMediaCollection('recent_photos')
        ->onlyKeepLatest(5);

    // Specific disk
    $this->addMediaCollection('downloads')
        ->useDisk('s3');

    // With conversions disk
    $this->addMediaCollection('photos')
        ->useDisk('s3')
        ->storeConversionsOnDisk('s3-thumbnails');

    // MIME type restriction
    $this->addMediaCollection('documents')
        ->acceptsMimeTypes(['application/pdf', 'application/zip']);

    // Custom validation
    $this->addMediaCollection('images')
        ->acceptsFile(function ($file) {
            return $file->mimeType === 'image/jpeg';
        });

    // Fallback URL/path when collection is empty
    $this->addMediaCollection('avatar')
        ->singleFile()
        ->useFallbackUrl('/images/default-avatar.jpg')
        ->useFallbackPath(public_path('/images/default-avatar.jpg'));

    // Enable responsive images for entire collection
    $this->addMediaCollection('hero_images')
        ->withResponsiveImages();

    // Collection-specific conversions
    $this->addMediaCollection('photos')
        ->registerMediaConversions(function () {
            $this->addMediaConversion('card')
                ->fit(Fit::Crop, 400, 400);
        });
}

Media Conversions

Define in registerMediaConversions():

use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Spatie\Image\Enums\Fit;

public function registerMediaConversions(?Media $media = null): void
{
    $this->addMediaConversion('thumb')
        ->fit(Fit::Contain, 300, 300)
        ->nonQueued();

    $this->addMediaConversion('preview')
        ->fit(Fit::Crop, 500, 500)
        ->withResponsiveImages()
        ->queued();

    $this->addMediaConversion('banner')
        ->fit(Fit::Max, 1200, 630)
        ->performOnCollections('images', 'headers')
        ->nonQueued()
        ->sharpen(10);

    // Conditional conversion based on media properties
    if ($media?->mime_type === 'image/png') {
        $this->addMediaConversion('png-thumb')
            ->fit(Fit::Contain, 150, 150);
    }

    // Keep original format instead of converting to jpg
    $this->addMediaConversion('web')
        ->fit(Fit::Max, 800, 800)
        ->keepOriginalImageFormat();

    // PDF page rendering
    $this->addMediaConversion('pdf-preview')
        ->pdfPageNumber(1)
        ->fit(Fit::Contain, 400, 400);

    // Video frame extraction
    $this->addMediaConversion('video-thumb')
        ->extractVideoFrameAtSecond(5)
        ->fit(Fit::Crop, 300, 300);
}

Image Manipulation Methods (via spatie/image)

Resizing and fitting:

  • width(int), height(int) — constrain dimensions
  • fit(Fit, int, int) — fit within bounds using Fit::Contain, Fit::Max, Fit::Fill, Fit::Stretch, Fit::Crop
  • crop(int, int) — crop to exact dimensions

Effects:

  • sharpen(int), blur(int), pixelate(int)
  • greyscale(), sepia()
  • brightness(int), contrast(int), colorize(int, int, int)

Orientation:

  • orientation(int), flip(string), rotate(int)

Format:

  • format(string)'jpg', 'png', 'webp', 'avif'
  • quality(int) — 1-100

Other:

  • border(int, string, string), watermark(string)
  • optimize(), nonOptimized()

Conversion Configuration

  • performOnCollections('col1', 'col2') — limit to specific collections
  • queued() / nonQueued() — run async or sync
  • withResponsiveImages() — also generate responsive variants for this conversion
  • keepOriginalImageFormat() — preserve png/webp/gif instead of converting to jpg
  • pdfPageNumber(int) — which PDF page to render
  • extractVideoFrameAtSecond(int) — video thumbnail timing

Retrieving Media

Getting media items

$media = $model->getMedia('images');                    // all in collection
$first = $model->getFirstMedia('images');               // first item
$last  = $model->getLastMedia('images');                // last item
$has   = $model->hasMedia('images');                    // boolean check

Getting URLs

$url     = $model->getFirstMediaUrl('images');           // original URL
$thumbUrl = $model->getFirstMediaUrl('images', 'thumb'); // conversion URL
$lastUrl  = $model->getLastMediaUrl('images', 'thumb');

Getting paths

$path     = $model->getFirstMediaPath('images');
$thumbPath = $model->getFirstMediaPath('images', 'thumb');

Temporary URLs (S3)

$tempUrl = $model->getFirstTemporaryUrl(
    now()->addMinutes(30),
    'images',
    'thumb'
);

Fallback URLs

$url = $model->getFallbackMediaUrl('avatar');

From the Media model

$media = $model->getFirstMedia('images');

$media->getUrl();                    // original URL
$media->getUrl('thumb');             // conversion URL
$media->getPath();                   // disk path
$media->getFullUrl();                // full URL with domain
$media->getTemporaryUrl(now()->addMinutes(30));
$media->hasGeneratedConversion('thumb');  // check if conversion exists

Filtering media

$media = $model->getMedia('images', function (Media $media) {
    return $media->getCustomProperty('featured') === true;
});

$media = $model->getMedia('images', ['mime_type' => 'image/jpeg']);

Custom Properties

Store arbitrary metadata on media items:

// When adding
$model->addMedia($file)
    ->withCustomProperties([
        'alt' => 'Descriptive text',
        'credits' => 'Photographer Name',
    ])
    ->toMediaCollection('images');

// Get/set on existing media
$media->setCustomProperty('alt', 'Updated text');
$media->save();

$alt = $media->getCustomProperty('alt');
$has = $media->hasCustomProperty('alt');
$media->forgetCustomProperty('alt');
$media->save();

Responsive Images

Generate multiple sizes for optimal loading:

// On the FileAdder
$model->addMedia($file)
    ->withResponsiveImages()
    ->toMediaCollection('images');

// On a conversion
$this->addMediaConversion('hero')
    ->fit(Fit::Max, 1200, 800)
    ->withResponsiveImages();

// On a collection
$this->addMediaCollection('photos')
    ->withResponsiveImages();

Using in Blade

{{-- Renders img tag with srcset --}}
{{ $media->toHtml() }}

{{-- With attributes --}}
{{ $media->img()->attributes(['class' => 'w-full', 'alt' => 'Photo']) }}

{{-- Get srcset string --}}
<img src="{{ $media->getUrl() }}" srcset="{{ $media->getSrcset() }}" />

{{-- Responsive conversion --}}
<img src="{{ $media->getUrl('hero') }}" srcset="{{ $media->getSrcset('hero') }}" />

Placeholder SVG

$svg = $media->responsiveImages()->getPlaceholderSvg(); // tiny blurred base64 placeholder

Managing Media

Clear a collection

$model->clearMediaCollection('images');

Clear except specific items

$model->clearMediaCollectionExcept('images', $mediaToKeep);

Delete specific media

$model->deleteMedia($mediaId);

Delete all media

$model->deleteAllMedia();

Delete model but keep media files

$model->deletePreservingMedia();

Reorder media

Media::setNewOrder([3, 1, 2]); // media IDs in desired order

Move/copy media between models

$media->move($otherModel, 'images');
$media->copy($otherModel, 'images');

Events

use Spatie\MediaLibrary\MediaCollections\Events\MediaHasBeenAddedEvent;
use Spatie\MediaLibrary\Conversions\Events\ConversionWillStartEvent;
use Spatie\MediaLibrary\Conversions\Events\ConversionHasBeenCompletedEvent;
use Spatie\MediaLibrary\MediaCollections\Events\CollectionHasBeenClearedEvent;

Listen to these events to hook into the media lifecycle:

Event::listen(MediaHasBeenAddedEvent::class, function ($event) {
    $event->media; // the added Media model
});

Event::listen(ConversionHasBeenCompletedEvent::class, function ($event) {
    $event->media;
    $event->conversion;
});

Configuration

Key config/media-library.php options:

return [
    'disk_name' => 'public',                    // default disk
    'max_file_size' => 1024 * 1024 * 10,         // 10MB
    'queue_connection_name' => '',                // queue connection
    'queue_name' => '',                          // queue name
    'queue_conversions_by_default' => true,       // queue conversions
    'media_model' => Spatie\MediaLibrary\MediaCollections\Models\Media::class,
    'file_namer' => Spatie\MediaLibrary\Support\FileNamer\DefaultFileNamer::class,
    'path_generator' => Spatie\MediaLibrary\Support\PathGenerator\DefaultPathGenerator::class,
    'url_generator' => Spatie\MediaLibrary\Support\UrlGenerator\DefaultUrlGenerator::class,
    'image_driver' => 'gd',                      // 'gd', 'imagick', or 'vips'
    'image_optimizers' => [/* optimizer config */],
    'version_urls' => true,                       // cache busting
    'default_loading_attribute_value' => null,     // 'lazy' for lazy loading
];

Custom Path Generator

use Spatie\MediaLibrary\Support\PathGenerator\PathGenerator;

class CustomPathGenerator implements PathGenerator
{
    public function getPath(Media $media): string
    {
        return md5($media->id) . '/';
    }

    public function getPathForConversions(Media $media): string
    {
        return $this->getPath($media) . 'conversions/';
    }

    public function getPathForResponsiveImages(Media $media): string
    {
        return $this->getPath($media) . 'responsive/';
    }
}

Custom File Namer

use Spatie\MediaLibrary\Support\FileNamer\FileNamer;

class CustomFileNamer extends FileNamer
{
    public function originalFileName(string $fileName): string
    {
        return Str::slug(pathinfo($fileName, PATHINFO_FILENAME));
    }

    public function conversionFileName(string $fileName, Conversion $conversion): string
    {
        return $this->originalFileName($fileName) . '-' . $conversion->getName();
    }

    public function responsiveFileName(string $fileName): string
    {
        return pathinfo($fileName, PATHINFO_FILENAME);
    }
}

Custom Media Model

use Spatie\MediaLibrary\MediaCollections\Models\Media as BaseMedia;

class Media extends BaseMedia
{
    // Add custom methods, scopes, or override behavior
}

Register in config: 'media_model' => App\Models\Media::class

Downloading Media

Single file

return $media->toResponse($request); // download
return $media->toInlineResponse($request); // display inline
return $media->stream(); // stream

ZIP download of collection

use Spatie\MediaLibrary\Support\MediaStream;

return MediaStream::create('photos.zip')
    ->addMedia($model->getMedia('images'));

Using with API Resources

class PostResource extends JsonResource
{
    public function toArray($request): array
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'image' => $this->getFirstMediaUrl('images'),
            'thumb' => $this->getFirstMediaUrl('images', 'thumb'),
            'media' => $this->getMedia('images')->map(function ($media) {
                return [
                    'id' => $media->id,
                    'url' => $media->getUrl(),
                    'thumb' => $media->getUrl('thumb'),
                    'name' => $media->name,
                    'size' => $media->size,
                    'type' => $media->mime_type,
                ];
            }),
        ];
    }
}