How to Upload Images in Laravel Using Spatie MediaLibrary (Complete Guide)

Avatar
M

Maksudur Rahman

Software Engineer••
7940Views
5mRead
0Reactions

Managing image uploads manually in Laravel often turns messy—handling disk storage, sanitizing file names, generating thumbnails, and maintaining database foreign keys quickly pollutes your controllers.

The Spatie Laravel MediaLibrary package standardizes this entire process. It associates files directly with Eloquent models, generates WebP conversions in the background, and provides clean methods to retrieve media URLs.

Here is the production-grade implementation for Laravel 11 and Laravel 12.


1. Installation and Database Migration

Install the package via Composer:

composer require "spatie/laravel-medialibrary:^11.0.0"

Publish the migration file and run it to create the central media table:

php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="medialibrary-migrations"
php artisan migrate

Make sure your public storage disk is linked to the public/ directory:

php artisan storage:link

2. Prepare the Eloquent Model

Add the HasMedia interface and the InteractsWithMedia trait to the model that will hold images (for example, App\Models\Post or App\Models\Product):

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Spatie\MediaLibrary\MediaCollections\Models\Media;

class Post extends Model implements HasMedia
{
    use HasFactory, InteractsWithMedia;

    protected $fillable = [
        'title',
        'slug',
        'content',
    ];

    /**
     * Define media collections and automatic image conversions.
     */
    public function registerMediaConversions(?Media $media = null): void
    {
        $this->addMediaConversion('thumb')
            ->width(300)
            ->height(200)
            ->format('webp')
            ->nonQueued(); // Use queued() for heavy background jobs

        $this->addMediaConversion('preview')
            ->width(800)
            ->height(500)
            ->format('webp')
            ->sharpen(5);
    }
}

Performance Tip: By converting uploaded images to .webp, you reduce file payload by 60–80% compared to raw JPEG/PNG uploads, directly improving Google Core Web Vitals (LCP).


3. Dedicated Form Request for Secure Validation

Never validate file uploads directly in controller closures. Create a dedicated FormRequest:

php artisan make:request StorePostRequest
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StorePostRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'title'   => ['required', 'string', 'max:255'],
            'content' => ['required', 'string'],
            'avatar'  => ['nullable', 'image', 'mimes:jpeg,png,jpg,webp', 'max:5120'], // 5MB max
            'gallery' => ['nullable', 'array', 'max:5'],
            'gallery.*' => ['image', 'mimes:jpeg,png,jpg,webp', 'max:5120'],
        ];
    }
}

4. Controller Implementation (Upload & Replace)

In your controller, use Spatie's fluent API to persist uploaded files:

<?php

namespace App\Http\Controllers;

use App\Http\Requests\StorePostRequest;
use App\Models\Post;
use Illuminate\Http\RedirectResponse;

class PostController extends Controller
{
    public function store(StorePostRequest $request): RedirectResponse
    {
        $post = Post::create($request->validated());

        // Single Image Upload (Avatar / Featured Image)
        if ($request->hasFile('avatar')) {
            $post->addMediaFromRequest('avatar')
                ->toMediaCollection('avatars');
        }

        // Multiple Images Upload (Gallery)
        if ($request->hasFile('gallery')) {
            foreach ($request->file('gallery') as $photo) {
                $post->addMedia($photo)
                    ->toMediaCollection('gallery');
            }
        }

        return redirect()->route('posts.show', $post)
            ->with('success', 'Post and images uploaded successfully!');
    }
}

If you want an avatar or featured image to always replace previous uploads rather than append to the collection, configure the collection as single-file in your model:

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

5. Retrieving Image URLs in Blade & API Resources

In Blade Views:

{{-- Get Original Image --}}
<img src="{{ $post->getFirstMediaUrl('avatars') }}" alt="{{ $post->title }}">

{{-- Get WebP Thumbnail Conversion --}}
<img src="{{ $post->getFirstMediaUrl('avatars', 'thumb') }}" alt="{{ $post->title }} Thumbnail">

{{-- Check if image exists before rendering --}}
@if($post->hasMedia('avatars'))
    <img src="{{ $post->getFirstMediaUrl('avatars', 'preview') }}">
@else
    <img src="/images/placeholder.webp" alt="Default Image">
@endif

{{-- Loop through multiple gallery images --}}
<div class="gallery-grid">
    @foreach($post->getMedia('gallery') as $mediaItem)
        <img src="{{ $mediaItem->getUrl('thumb') }}" alt="Gallery Image">
    @endforeach
</div>

In Laravel API Resource:

public function toArray($request): array
{
    return [
        'id'        => $this->id,
        'title'     => $this->title,
        'avatar'    => $this->getFirstMediaUrl('avatars'),
        'thumbnail' => $this->getFirstMediaUrl('avatars', 'thumb'),
        'gallery'   => $this->getMedia('gallery')->map(fn($m) => [
            'id'  => $m->id,
            'url' => $m->getUrl(),
            'thumb' => $m->getUrl('thumb'),
        ]),
    ];
}

Troubleshooting Common Errors

Error

Cause

Fix

The file was not uploaded

PHP POST size limit exceeded

Increase upload_max_filesize and post_max_size in php.ini.

404 on Image URL

Storage symlink missing

Run php artisan storage:link.

GD / Imagick driver error

Missing PHP image extension

Install php-gd or php-imagick (sudo apt install php8.3-gd).

Recommended Resources & Courses

Partner Links

React to this article