Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Medialibrary Laravel Package

spatie/laravel-medialibrary

Associate files with Eloquent models via a simple API: add uploads or local paths to media collections, store them on any Laravel filesystem, and generate image manipulations for images and PDFs. Ideal for managing media in Laravel apps.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and publish its assets:

composer require spatie/laravel-medialibrary
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="medialibrary-migrations"
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="medialibrary-config"
php artisan migrate
php artisan storage:link

Look first at config/media-library.php, the published migration, and Spatie\MediaLibrary\MediaCollections\Models\Media. Then read the package docs at https://spatie.be/docs/laravel-medialibrary/v11.

Add media support to a model by implementing HasMedia and using InteractsWithMedia:

use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;

class User extends Authenticatable implements HasMedia
{
    use InteractsWithMedia;

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

A first practical use case is a user avatar. Validate the upload in a form request, attach it, and render it:

$user->addMediaFromRequest('avatar')->toMediaCollection('avatar');
<img src="{{ $user->getFirstMediaUrl('avatar') }}" alt="{{ $user->name }}">

That is the core day-to-day loop: define a collection on the model, add a file to it, then read URLs from the model.

Implementation Patterns

Configure collections and conversions on the model. Keep media rules close to the owning model:

public function registerMediaCollections(): void
{
    $this->addMediaCollection('avatar')
        ->singleFile()
        ->acceptsMimeTypes(['image/jpeg', 'image/png', 'image/webp']);

    $this->addMediaCollection('images');

    $this->addMediaCollection('downloads')
        ->useDisk('s3');
}

public function registerMediaConversions(Media $media = null): void
{
    $this->addMediaConversion('thumb')
        ->width(368)
        ->height(232)
        ->performOnCollections('avatar', 'images')
        ->nonQueued();

    $this->addMediaConversion('large')
        ->width(1200)
        ->withResponsiveImages()
        ->performOnCollections('images');
}

Use collection config for disk choice, and the explicit disk argument only when needed. Prefer useDisk('s3') in registerMediaCollections() so controllers stay simple. If a collection can go to different disks, toMediaCollection('downloads', 's3') works.

Normalise upload handling in controllers or actions. Use Laravel validation for size and MIME type first; the package is storage/manipulation, not upload validation:

$request->validate([
    'image' => ['required', 'image', 'max:5120'],
]);

$media = $post->addMediaFromRequest('image')
    ->usingName($request->input('title'))
    ->withCustomProperties(['alt' => $request->input('alt')])
    ->toMediaCollection('images');

Other useful adders include addMedia($request->file('image')), addMediaFromUrl(), addMediaFromDisk(), addMediaFromBase64(), and addMediaFromString().

Retrieve media consistently. Use getFirstMedia() when you need the model, getFirstMediaUrl() for a URL, and getMedia() for galleries:

$avatarUrl = $user->getFirstMediaUrl('avatar', 'thumb');
$images = $post->getMedia('images');
$firstImage = $post->getFirstMedia('images');

$srcset = $post->getFirstMedia('images')?->getSrcset('large');

For private S3 files, generate temporary URLs rather than exposing the disk path:

$url = $media->getTemporaryUrl(now()->addMinutes(10), 'thumb');

Expose media deliberately in APIs. Do not rely on raw model serialization for public APIs. Map media to a small DTO-like array:

'images' => $post->getMedia('images')->map(fn (Media $media) => [
    'url' => $media->getUrl(),
    'thumb' => $media->getUrl('thumb'),
    'alt' => $media->getCustomProperty('alt'),
])->values(),

Use custom properties for editorial metadata. withCustomProperty(), setCustomProperty(), and getCustomProperty() are useful for alt text, captions, credits, and external IDs without adding another table.

Queue conversions in production, but keep critical conversions synchronous where needed. The package can queue conversions; ensure workers are running. For small avatar thumbnails or tests, nonQueued() avoids missing-conversion surprises. After changing conversion definitions, regenerate existing files with:

php artisan media-library:regenerate

Clean up intentionally. singleFile() collections replace the previous file when a new one is added. For multi-file collections, remove individual items with $media->delete() or wipe a collection with $model->clearMediaCollection('images'). If deleting a model should preserve files, use deletePreservingMedia() where appropriate.

Gotchas and Tips

  • Public disk URLs need the storage link. If getFirstMediaUrl() returns a path but the browser 404s, check php artisan storage:link and APP_URL. For S3, check bucket visibility and whether you need temporary URLs.
  • getFirstMediaUrl() returns an empty string when no media exists. Use a fallback in Blade or a default asset; do not assume it returns null.
  • Collection names are plain strings. Typos are easy and can silently create unexpected behaviour. Define constants or an enum-like class for collection names used across controllers, views, and tests.
  • Validate before adding media. acceptsMimeTypes() filters collections, but it is not a replacement for request validation. Validate size, MIME type, and image dimensions where relevant.
  • Queued conversions fail quietly if no worker is running. Thumbnails or responsive variants may simply be absent. Check the queue, failed jobs, and the media record’s generated conversions. Use nonQueued() for conversions that must exist immediately.
  • Image driver and format support matter. GD and Imagick support different formats and options. WebP/AVIF and PDF conversions depend on the driver and related binaries. Test conversion output in an environment close to production.
  • Config caching can hide changes. After editing config/media-library.php, especially custom path generators, file namers, or media model bindings, run php artisan config:clear locally and redeploy config cache correctly.
  • Do not hardcode storage paths. Use getUrl(), getTemporaryUrl(), and getFirstMediaUrl() rather than concatenating storage/ paths. This keeps local, S3, and CDN setups portable.
  • Regenerate after conversion changes. Existing media do not automatically get new conversion definitions. Run php artisan media-library:regenerate after changing widths, formats, or conversion names.
  • Testing needs real image fixtures. UploadedFile::fake()->image('avatar.jpg') works well; Storage::fake('public') isolates files. If conversions are queued, either use Queue::fake() plus assertions or mark the conversion nonQueued() for deterministic tests.
  • Extension points are resolved through the container. Custom Media models, path generators, file namers, image generators, MIME type detectors, and observers can be bound in config and overridden in the service container. This is the right place to customise naming, storage paths, or generated URLs without forking the package.
  • Deleting models and media deserves a deliberate policy. Know whether your app should delete files with the model or preserve them. Use deletePreservingMedia() when records are soft-deleted or archived but files must remain.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
bernskioldmedia/laravel-matomo
bernskioldmedia/laravel-harvest
bernskiold/laravel-data-scrubber
bernskiold/laravel-currency-converter
bernskiold/laravel-campaign-monitor
bernskioldmedia/laravel-autotranslate
siberfx/telegram-bot
roy-sandip/laravel-view-extended
gregsphotography/site-builder-core
roy-sandip/laravel-view
anodyne/blade-fluentui-icons
webx-ui/module-catalog-collections
webx-ui/module-catalog-feeds
makidizajnerica/laravel-dto
matviib/scheduler
webx-ui/templates
anodyne/blade-tabler-icons
bref/laravel-websockets
anodyne/blade-hugeicons
1994/flatpack-statamic