How to Upload Images With Spatie Media Library in Laravel

The Spatie Media Library simplifies the process of handling media files, such as images, in Laravel. It provides features for storage, manipulation, and retrieval of media files. One of the strengths of the package is that you can easily associate uploaded files with your Eloquent Models.

In this guide, you will learn how to integrate the Media Library package into an application. We’ll use it to associate uploaded images with an Eloquent Model, specifically the Product model.

We’ll cover all the required steps like the package installation process, model setup, defining a migration, creating the controller, views and routes to show products, show a product form with upload and storing new products.

Let’s get started!

Step 1: Install Laravel

Begin by creating a new Laravel project using Composer by running:

composer create-project --prefer-dist laravel/laravel product-media-demo

Step 2: Install Spatie Media Library

Next, install the Spatie Media Library package via Composer. Run the following command in your terminal:

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

After this has finished, we need to run following command to publish a migration the media library requires to work properly:

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

What this publishing does is it creates a file like, for example database/migrations/2024_02_26_203939_create_media_table.php . This migration serves to define a table ‘media’ with all the columns predefined as they are required by media library.

Afterwards we need to execute this migration by running:

php artisan migrate

The Spatie Media Library has now been successfully installed.

Step 3: Create Model and Migration

Now, let’s create a “Product” model along with its migration by running:

php artisan make:model Product -m

Step 4: Add Migration Code

Open the generated migration file (database/migrations/YYYY_MM_DD_create_products_table.php) and add additional columns that could be encountered in a real-world product application, such as “name,” “description,” “price,” and “image”:

database/migrations/2024_02_25_184519_create_products_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up()
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->text('description')->nullable();
            $table->decimal('price', 8, 2);
            $table->string('image')->nullable(); // Added for image path
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('products');
    }
};

Step 5: Run Migration

Now, run the migration to create the products table in the database:

php artisan migrate

Step 6: Add Model Code

Open the “Product” model (app\Models\Product.php) and configure it to use the Spatie Media Library trait and implement the HasMedia interface.

app\Models\Product.php
<?php

namespace App\Models;

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

class Product extends Model implements HasMedia
{
    use InteractsWithMedia;

    protected $fillable = ['name', 'description', 'price', 'image'];
}

Defining our Model this way allows us to associate media, like an uploaded image, with each instance of a Product. We’ll see exactly how this is used, when we add our controller code in step 9.

Step 7: Create Controller

Generate a controller named “ProductController” by running the following Artisan command:

php artisan make:controller ProductController

Step 8: Add Controller Code

Now open the generated “ProductController” (app\Http\Controllers\ProductController.php) and implement the methods below to list an index page with products, show a create product form and finally storing a new product along with its uploaded image:

app/Http/Controllers/ProductController.php
<?php

namespace App\Http\Controllers;

use App\Models\Product;
use Illuminate\Http\Request;

class ProductController extends Controller
{
    // Display a list of created products along with their images
    public function index()
    {
        $products = Product::latest()->paginate(10);
        return view('products.index', compact('products'));
    }

    // Display the form to create a new product
    public function create()
    {
        return view('products.create');
    }

    // Store a new product along with its uploaded image file
    public function store(Request $request)
    {
        $request->validate([
            // Sets a max image upload file size of 2048 kilobytes or 2MB, adjust as needed:
            'image' => 'required|image|max:2048',
            // Validate that other fields contain proper values too:
            'name' => 'required|string|max:255',
            'description' => 'nullable|string|max:255',
            'price' => 'required|numeric|min:0|max:999999.99',
        ]);

        // First create the product in the database using the Eloquent model
        $product = Product::create($request->validated());

        // Then associate the uploaded image file with the product
        $product->addMediaFromRequest('image')->toMediaCollection('product-images');

        // Send the user back to the product list page
        return redirect(route('products.index'))->with('success', 'Product created successfully.');
    }
}

Step 9: Add a View to Create a Product

In this step we’ll create a view that contains the form to add a new product along with an uploaded image.

I made sure the blade code offers a nicely layouted form that shows error messages for missing or invalid values. This way we can guarantee the user is prompted to always upload an actual image file and fill the required fields name and price.

Create a file at resources/views/products/create.blade.php and add the following code:

resources/views/products/create.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Excel Import</title>
    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
    <h1 class="my-4">Create Product</h1>
    <form action="{{ route('products.store') }}" method="POST" enctype="multipart/form-data">
        @csrf
        <div class="mb-3">
            <label for="name" class="form-label">Name</label>
            @error('name')
            <div class="text-danger">
                <small>{{ $message }}</small>
            </div>
            @enderror
            <input type="text" class="form-control" id="name" name="name">
        </div>
        <div class="mb-3">
            <label for="description" class="form-label">Description</label>
            @error('description')
            <div class="text-danger">
                <small>{{ $message }}</small>
            </div>
            @enderror
            <textarea class="form-control" id="description" name="description"></textarea>
        </div>
        <div class="mb-3">
            <label for="price" class="form-label">Price</label>
            @error('price')
            <div class="text-danger">
                <small>{{ $message }}</small>
            </div>
            @enderror
            <input type="number" step="0.01" class="form-control" id="price" name="price">
        </div>
        <div class="mb-3">
            <label for="image" class="form-label">Product Image</label>
            @error('image')
            <div class="text-danger">
                <small>{{ $message }}</small>
            </div>
            @enderror
            <input type="file" class="form-control" id="image" name="image">
        </div>
        <button type="submit" class="btn btn-primary">Create Product</button>
    </form>
</div>

</body>
</html>

Step 10: Create a View to Show Products

In this step we’ll create a view that allows us to show all products. We will use Spatie Media Library to render a thumbnail of the uploaded product image.

Create a file at resources/views/products/index.blade.php and add the following code:

resources/views/products/index.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Excel Import</title>
    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">

    <!-- Display success message if it was set-->
    @if(session('success'))
        <div class="alert alert-success">{{ session('success') }}</div>
    @endif

    <h1 class="my-4">Product Listing</h1>
    <table class="table">
        <thead>
        <tr>
            <th colspan="3"></th>
            <th class="text-end">
                <a href="{{ route('products.create') }}" class="btn btn-primary">Create Product</a>
            </th>
        </tr>
        <tr>
            <th>Name</th>
            <th>Description</th>
            <th>Price</th>
            <th>Image</th>
        </tr>
        </thead>
        <tbody>
        @foreach($products as $product)
            <tr>
                <td>{{ $product->name }}</td>
                <td>{{ $product->description }}</td>
                <td>{{ $product->price }}</td>
                <!-- Fetch a thumbnail image based on the product's associated media -->
                <td><img src="{{ $product->getFirstMediaUrl('product-images', 'thumb') }}" alt="Product Image" width="140px"></td>
            </tr>
        @endforeach
        </tbody>
    </table>
    {{ $products->links() }}
</div>

</body>
</html>

Step 11: Define Routes

Finally, before we can test the application, we need to define the routes to list products, show the create form, and store a product.

Open routes/web.php and add:

routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ProductController;

Route::get('/products', [ProductController::class, 'index'])->name('products.index');
Route::get('/products/create', [ProductController::class, 'create'])->name('products.create');
Route::post('/products', [ProductController::class, 'store'])->name('products.store');

Step 12: Test the Application

You can now test the application by running the development server:

php artisan serve

Now you can navigate to http://127.0.0.1:8000/products in your browser to view the product listing. Proceed to http://127.0.0.1:8000/products/create to access the create product form.

Screenshot of the Create Product Form with Image Upload

Select an image to upload and fill in the other product details and submit the form. Verify that the product and its associated image are successfully stored in the database.

After storing the form your database contents should look somewhat like this:

Screenshot Showing the Records Stored in products and Associated media Table

Now if you continue and upload a few more you’ll see it nicely displays all the thumbnail images on the product page:

That’s it! You’ve successfully built your first application that uses Spatie Media Library to manage its uploaded media files.

Conclusion

In this tutorial, we’ve explored how to upload images using the Spatie Media Library package in a Laravel application. By following the steps outlined above, you can easily handle image uploads and storage in your Laravel projects.

Using third party packages like this can greatly speed up your development process. Before you use a package in your production application, check that the package is well maintained, supports your Laravel version, is properly documented and does not have lots of unresolved issues posted on their git page.

Spatie is a well-known company that has been developing and maintaining numerous packages for years and provides them for free as open source. The Media Library is one of their most widely used packages and is likely to meet most quality requirements.

I hope this helped you get started in using this handy third party package. Let me know in the comments what you’re using it for, how it went and what issues you may have had with it.

Happy Coding!

References

Johan van den Broek

Johan is the creator of laracoding.com. As a child, he began tinkering with various programming languages, many of which have been long forgotten today. Currently, he works exclusively with PHP and Laravel, and his passion for programming remains to this day.

Leave a Reply

Your email address will not be published. Required fields are marked *

Recent Posts