Skip to content

[13.x] Adds first-party support for image processing#59276

Open
nunomaduro wants to merge 62 commits into13.xfrom
feat/image
Open

[13.x] Adds first-party support for image processing#59276
nunomaduro wants to merge 62 commits into13.xfrom
feat/image

Conversation

@nunomaduro
Copy link
Member

@nunomaduro nunomaduro commented Mar 18, 2026

This PR adds first-party support for image processing in Laravel — a driver-based, immutable API for manipulating images from uploads, storage, file paths, URLs, or raw bytes.

$request->image('avatar') // 2.1 MB (png)
    ->optimize() // webp, quality 75
    ->store('avatars'); // 75.8 KB (webp) -96.2% 🔥🔥🔥

You may also create multiple variants from the same upload.

$image = $request->image('photo');

// 2.1 MB (png) -> 5.8 KB (webp) -99.7% 🥵
$image->cover(200, 200)->toWebp()->store('thumbs'); 

// 2.1 MB (png) -> 67.8 KB (webp) -96.6% 🥵
$image->greyscale()->toWebp()->store('greys');

You may even create thumbnails from existing images — for example, in a queued job.

Post::each(fn (Post $post) => $post->update([
    'thumbnail' => Storage::image($post->photo)
        ->cover(200, 200)
        ->toWebp()
        ->store('thumbnails'),
]));

There’s a lot in this pull request, so bellow is the documentation preview:

Introduction

Laravel provides a powerful, driver-based image processing API that makes it simple to manipulate images from uploads, storage, file paths, or raw bytes. Out of the box, Laravel includes support for processing images via GD, Imagick, and Cloudflare Images.

Configuration

Laravel's image processing configuration file is located at config/image.php. Within this file, you may configure the default image processing driver as well as driver-specific settings:

'default' => env('IMAGE_DRIVER', 'gd'),

Note

The config/image.php configuration file is included by default in new Laravel 13 applications. If your application does not contain this file, you may publish it using the config:publish Artisan command:

php artisan config:publish image

Driver Prerequisites

GD and Imagick Drivers

The GD and Imagick drivers are powered by Intervention Image v3. Before using either driver, you will need to install the Intervention Image package via the Composer package manager:

composer require intervention/image:^3.11.7

The GD driver requires the GD PHP extension, while the Imagick driver requires the Imagick PHP extension.

Note

Processing images locally with GD or Imagick can be memory intensive, especially for large images.

Cloudflare Driver

The Cloudflare driver processes images remotely via the Cloudflare Images API. Each image is temporarily uploaded to Cloudflare, transformed via flexible variants, and deleted after the transformed result is downloaded.

Before using the Cloudflare driver, you must enable flexible variants in your Cloudflare dashboard:

  1. Navigate to Images > Variants in your Cloudflare dashboard.
  2. Enable Flexible variants.

Warning

Enabling flexible variants allows anyone with your account's image delivery URL to obtain images with any transformation applied. Do not enable this feature if you rely on variant-based access control for sensitive images.

To use the Cloudflare driver, you will need to set the IMAGE_CLOUDFLARE_ACCOUNT_ID and IMAGE_CLOUDFLARE_API_TOKEN environment variables:

IMAGE_CLOUDFLARE_ACCOUNT_ID=your-account-id
IMAGE_CLOUDFLARE_API_TOKEN=your-api-token

Warning

Unlike the GD and Imagick drivers, the Cloudflare driver is not memory-intensive because all image processing happens remotely. However, this comes with trade-offs. Each operation requires multiple HTTP requests (uploading, transforming, and deleting), which can make it significantly slower. Additionally, the Cloudflare driver may not produce results that exactly match the original request. While it attempts to follow the specified image transformations, the output can differ in noticeable ways—such as variations in format, encoding, compression, or small discrepancies in dimensions due to rounding.

The Cloudflare driver does not support BMP input images, so when using the Cloudflare driver, ensure your upload validation restricts files to the supported formats:

use Illuminate\Validation\Rules\File;

$request->validate([
    'avatar' => ['required', 'image', File::types(['jpg', 'png', 'gif', 'webp'])],
]);

Pruning Orphaned Images

If the PHP process is terminated during Cloudflare image processing, the temporary image may not be deleted from Cloudflare. You may clean up these orphaned images using the pruneOrphaned method. Images are identified by a configurable prefix (laravel-image by default) and only images older than 5 minutes are removed. You may schedule this in your application's routes/console.php file:

use Illuminate\Support\Facades\Image;

Schedule::call(function () {
    Image::pruneOrphaned('cloudflare');
})->hourly();

Obtaining Image Instances

There are several ways to obtain an Image instance. The most common is from an uploaded file on the request:

$image = $request->image('avatar');

You may also create an image from a file path, a URL, raw bytes, a base64 string, a storage disk, or a Stringable:

use Illuminate\Support\Facades\Image;

// From a file path...
$image = Image::fromPath('/path/to/photo.jpg');

// From a URL...
$image = Image::fromUrl('https://example.com/photo.jpg');

// From raw bytes...
$image = Image::fromBytes($bytes);

// From a base64 encoded string...
$image = Image::fromBase64($base64);

// From a storage disk...
$image = Storage::disk('s3')->image('photos/avatar.jpg');

Manipulating Images

The Image class provides several methods for common image manipulations. Each method returns a new immutable instance — the original is never modified:

cover

The cover method resizes and crops the image to exactly fill the given dimensions, maintaining aspect ratio:

$image->cover(200, 200);

scale

The scale method resizes the image proportionally to fit within the given dimensions. Images are never upscaled — if the source is smaller than the target, the original dimensions are preserved:

$image->scale(1200, 800);

orient

The orient method auto-orients the image based on its EXIF data. This is useful for correcting photos taken on phones that appear rotated:

$image->orient();

blur

The blur method applies a blur effect to the image. The amount may range from 0 to 100:

$image->blur(15);

greyscale

The greyscale method converts the image to greyscale:

$image->greyscale();

sharpen

The sharpen method sharpens the image. This is particularly useful after downscaling, as resized images can appear soft. The amount may range from 0 to 100:

$image->sharpen(10);

flip / flop

The flip method mirrors the image vertically, while the flop method mirrors it horizontally:

$image->flip();
$image->flop();

Converting Images

toWebp / toJpg

The toWebp and toJpg methods convert the image to WebP or JPEG format, respectively:

$image->toWebp();
$image->toJpg();

quality

The quality method sets the output quality for lossy formats. The value should be between 1 and 100:

$image->toWebp()->quality(85);

optimize

The optimize method is a shortcut for setting both the format and quality in a single call:

$image->optimize();              // WebP at quality 75
$image->optimize('jpg', 90);     // JPEG at quality 90

Retrieving Image Information

After processing, you may retrieve information about the resulting image:

$image->mimeType();    // 'image/webp'
$image->extension();   // 'webp'
$image->dimensions();  // [200, 200]
$image->width();       // 200
$image->height();      // 200

The toBytes method returns the raw processed image bytes. The toBase64 and toDataUri methods return encoded representations — useful for inline images or lazy-load placeholders:

$bytes = $image->toBytes();
$base64 = $image->toBase64();
$dataUri = $image->toDataUri(); // data:image/webp;base64,...

Storing Images

The Image class provides the same storage methods as UploadedFile. The store method will store the processed image with a randomly generated filename:

$path = $image->cover(200, 200)->toWebp()->store('avatars');

Specifying a File Name

If you would like to specify a file name, you may use the storeAs method:

$path = $image->storeAs('avatars', 'avatar.webp');

Specifying a Disk

You may pass the disk name as the second argument to store, or as the third argument to storeAs:

$path = $image->store('avatars', 's3');
$path = $image->storeAs('avatars', 'avatar.webp', 's3');

Public Visibility

The storePublicly and storePubliclyAs methods store the image with public visibility:

$path = $image->storePublicly('avatars', 's3');
$path = $image->storePubliclyAs('avatars', 'avatar.webp', 's3');

Hash Names

The hashName method returns a hashed filename with the correct extension based on the processed image's format:

$image->hashName();           // 'a1b2c3d4e5...xyz.webp'
$image->hashName('avatars');  // 'avatars/a1b2c3d4e5...xyz.webp'

Specifying the Driver

By default, images are processed using the driver configured in config/image.php. You may override the driver for a specific image using the using method or one of the convenience shortcuts:

$image->using('imagick')->cover(200, 200);

// Or use the shortcuts...
$image->usingGd()->cover(200, 200);
$image->usingImagick()->cover(200, 200);
$image->usingCloudflare()->cover(200, 200);

Custom Image Drivers

You may register your own image processing driver using the extend method on the Image facade. The extend method accepts a driver name and a closure that receives the application instance and should return an implementation of Illuminate\Contracts\Image\Driver:

use Illuminate\Support\Facades\Image;

Image::extend('custom', function ($app) {
    return new CustomImageDriver;
});

The Driver contract requires a single process method:

namespace Illuminate\Contracts\Image;

use Illuminate\Foundation\Image\PendingImageOptions;

interface Driver
{
    public function process(string $contents, PendingImageOptions $options): string;
}

@nunomaduro nunomaduro requested a review from taylorotwell March 25, 2026 18:10
@nunomaduro nunomaduro self-assigned this Mar 25, 2026
@nunomaduro nunomaduro marked this pull request as ready for review March 25, 2026 18:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants