Import Excel with Images in Laravel using Maatwebsite Excel & PhpSpreadsheet

Learn how to import Excel files with images in Laravel using Maatwebsite Excel and PhpSpreadsheet. Step-by-step guide to extract images, validate rows, and store data with images into your database.

Muhammad Ishaq
Muhammad Ishaq
03 Aug 2025
4 minute read
Import Excel with Images in Laravel using Maatwebsite Excel & PhpSpreadsheet

Uploading data through Excel files is a common feature in modern web applications. But what if your Excel file contains images along with data, and you want to save both the data and images into your Laravel application?

In this article, you'll learn how to import Excel data with embedded images in Laravel using the powerful Maatwebsite Excel package and the low-level capabilities of PhpSpreadsheet.

We'll cover:

  1. Reading Excel data row by row

  2. Extracting and saving images from Excel

  3. Storing data into related models

  4. Validating imported data

  5. Saving images in your public directory

Let’s dive in.

Prerequisites

Make sure you have the following setup:

  • Laravel 8+

  • maatwebsite/excel package installed:

composer require maatwebsite/excel
  • PhpSpreadsheet (comes bundled with maatwebsite/excel)


Project Use Case

Imagine you're building a CRM or leaderboard system where each row in your Excel file includes:

  1. A Name

  2. A Email

  3. A phone

  4. An Image (avatar) embedded inside the Excel


Step-by-Step: Laravel Excel Import with Images

Here's how you can achieve this using a custom import class.

Create the Import Class

php artisan make:import UserImport

Then open the newly created file and modify it like below.

namespace App\Imports;

use App\Models\User;
use Illuminate\Support\Collection;
use Maatwebsite\Excel\Concerns\ToCollection;
use Maatwebsite\Excel\Concerns\WithValidation;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;

class UserImport implements ToCollection, WithValidation
{
    public function collection(Collection $rows)
    {
        $spreadsheet = IOFactory::load(request()->file('file'));
        $images = $spreadsheet->getActiveSheet()->getDrawingCollection();

        foreach ($rows as $key => $row) {
            if ($key === 0) continue; // Skip the heading row

            // Step 1: Extract and save the image
            if ($images[$key - 1] instanceof MemoryDrawing) {
                ob_start();
                call_user_func(
                    $images[$key - 1]->getRenderingFunction(),
                    $images[$key - 1]->getImageResource()
                );
                $imageContents = ob_get_contents();
                ob_end_clean();

                $extension = match ($images[$key - 1]->getMimeType()) {
                    MemoryDrawing::MIMETYPE_PNG => 'png',
                    MemoryDrawing::MIMETYPE_GIF => 'gif',
                    MemoryDrawing::MIMETYPE_JPEG => 'jpg',
                    default => 'jpg'
                };
            } else {
                $reader = fopen($images[$key - 1]->getPath(), 'r');
                $imageContents = stream_get_contents($reader);
                fclose($reader);
                $extension = $images[$key - 1]->getExtension();
            }

            $filename = time() . ($key - 1) . '.' . $extension;
            $path = public_path("admin/images/users/$filename");
            file_put_contents($path, $imageContents);
            $imageUrl = "/admin/images/users/$filename";

            // Step 2: Create or update the user
            User::updateOrCreate(
                ['email' => $row[1]],
                [
                    'name'   => $row[0],
                    'phone'  => $row[2],
                    'avatar' => $imageUrl,
                ]
            );
        }

        return true;
    }

    public function rules(): array
    {
        return [
            '0' => 'required|string',   // Name
            '1' => 'required|email',    // Email
            '2' => 'required|string',   // Phone
        ];
    }

    public function customValidationMessages()
    {
        return [
            '0.required' => 'Name is required.',
            '1.required' => 'Email is required.',
            '1.email'    => 'Invalid email format.',
            '2.required' => 'Phone is required.',
        ];
    }
}

Use the Import in Controller

use App\Imports\UserImport;
use Maatwebsite\Excel\Facades\Excel;
 
public function import()
{
    request()->validate([
        'file' => 'required|mimes:xlsx,xls'
    ]);

    Excel::import(new UserImport, request()->file('file'));

    return back()->with('success', 'Data imported successfully with images.');

}

 

Make sure the first row is the heading, and each image is placed properly in the corresponding row and column.


Common Issues & Tips

  • Image Index Misalignment: Images array is usually 0-indexed and starts below the heading, so using $key - 1 aligns rows with drawings.

  • Folder Permissions: Ensure public/admin/images/users  directory exists and is writable.

  • Large Files: For large files, consider queueing your import job using WithChunkReading and ShouldQueue interfaces.

  • Image Format Errors: PhpSpreadsheet supports JPEG, PNG, GIF. Avoid inserting BMP or SVG images.


Final Thoughts

Importing Excel data with images in Laravel may seem tricky at first, but with the combination of Maatwebsite Excel and PhpSpreadsheet, it's totally achievable.

Let your users upload full Excel files - and handle everything programmatically behind the scenes.