Laravel 13.20.0 adds first-party image processing to the framework, with an immutable, driver-based API for transforming images that come from uploads, storage, URLs, or raw bytes. The release also brings a WithoutMiddleware controller attribute, a dedicated session prefix for Redis, and a batch of Eloquent and queue additions.
- First-party image processing via the new
Imagefacade - A new
#[WithoutMiddleware]controller attribute - Configure a separate Redis prefix for sessions
- Quietly increment and decrement on Eloquent models
- Enums accepted as
WithoutOverlappingqueue keys - And more
What's New
First-Party Image Processing
Laravel 13.20 introduces an Illuminate\Image component that handles resizing, cropping, format conversion, and storage without reaching for a third-party wrapper.
Images are immutable: every transformation returns a new instance, and the queued transformations are applied when you ask for the result.
The most common path starts with an upload. Request::image() returns an Image instance for a given file key, or null if the key does not hold an uploaded file:
$request->image('avatar') ->cover(200, 200) ->toWebp() ->store('avatars');
You can also build an image from a path, a URL, raw bytes, base64, or a storage disk:
Image::fromPath('/path/to/photo.jpg');Image::fromUrl('https://example.com/photo.jpg');Image::fromBytes($bytes);Image::fromStorage('photos/avatar.jpg', 's3'); Storage::disk('s3')->image('photos/avatar.jpg');
Transformations, output options, and inspection methods are all available on the instance:
// Transformations...$image->cover(200, 200);$image->contain(800, 600);$image->crop(200, 200);$image->resize(1024, 768);$image->scale(1200, 800);$image->rotate(90);$image->blur(10);$image->sharpen(10);$image->grayscale();$image->flip();$image->flop();$image->orient(); // Applies EXIF rotation... // Output format and quality...$image->toWebp();$image->toJpg()->quality(80);$image->optimize(); // WebP at quality 70...$image->optimize('jpg', 85); // Reading and inspecting...$image->toBytes();$image->toBase64();$image->toDataUri();$image->width();$image->height();$image->dimensions();$image->mimeType();$image->extension();
Because instances are immutable, you can branch from one source image to produce several variants:
$image = $request->image('photo'); $image->cover(200, 200)->toWebp()->store('thumbnails');$image->grayscale()->toWebp()->store('grayscale');
Two drivers ship with the component, both backed by Intervention Image v4: GD and Imagick. You can pick a driver per image, and the Image facade lets you override how a driver handles a given transformation:
use Illuminate\Image\Transformations\Sharpen; // Pick a driver per image...$image->using('imagick');$image->usingGd();$image->usingImagick(); // Override how a driver handles a transformation...Image::transformUsing('gd', Sharpen::class, function ($image, Sharpen $sharpen) { // Custom sharpen handling for the GD driver... return $image;});
Intervention Image is a suggested dependency rather than a required one, so you install it yourself:
composer require intervention/image
See #59276.
A #[WithoutMiddleware] Controller Attribute
The routing attributes gain a counterpart to #[Middleware]. Where the latter attaches middleware to a controller class or method, #[WithoutMiddleware] excludes it, giving you the "excluding middleware" behavior of route groups at the attribute level:
use App\Http\Middleware\EnsureTokenIsValid;use Illuminate\Routing\Controllers\Attributes\Middleware;use Illuminate\Routing\Controllers\Attributes\WithoutMiddleware; #[Middleware(EnsureTokenIsValid::class)]class UserController{ public function index() { // Middleware applies here... } #[WithoutMiddleware(EnsureTokenIsValid::class)] public function profile() { // ...but not here. }}
Matching uses ReflectionAttribute::IS_INSTANCEOF, so subclasses of a middleware are excluded too. See #60709.
Redis Session Prefix
Applications that point both SESSION_DRIVER and CACHE_STORE at Redis have until now had sessions inherit the cache prefix, which makes session keys hard to pick out when you are cleaning up or debugging a shared keystore. A new prefix option in config/session.php lets you set a separate one:
'prefix' => env('SESSION_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-session-'),
The cache store is cloned before the session prefix is applied, so setting it does not affect cache keys. The option is opt-in and mirrors the existing session.connection setting. See #60700.
Quiet Bulk Increments on Eloquent
Eloquent already had incrementQuietly() and decrementQuietly() for single columns. This release fills in the multi-column gap with incrementEachQuietly() and decrementEachQuietly(), which update several columns at once while suppressing model events:
$user->incrementEachQuietly(['posts_count' => 1, 'points' => 10]); $user->decrementEachQuietly(['credits' => 3, 'tokens' => 2]);
See #60720 and the follow-up fix for dynamic calls in #60737.
Enums as Queue Overlap Keys
The WithoutOverlapping job middleware now accepts a PHP enum as its key, so jobs keyed by an enumerated value no longer need a manual conversion to a string:
class UpdateCategory implements ShouldQueue{ use Queueable; public function __construct(protected Category $category) {} public function middleware(): array { return [new WithoutOverlapping($this->category)]; }}
The change is backward compatible with existing string keys. See #60722.
Other Fixes and Improvements
beforePushing()andafterPushing()callbacks onQueueFake(#60689), andMailFake::assertQueuedTimes()is now public (#60710)assertEmpty()added to theStoragefacade (#60658), and memory usage reported on theWorkerStoppingevent (#60613)make:migrationnow generates collision-free, ordered timestamp prefixes (#60771)#[SensitiveParameter]applied to parameters carrying secrets, keeping them out of stack traces (#60753)- A
capitalizeparameter forStringable::initials()(#60741) Str::containsAll()no longer returnstruefor an empty needles array (#60746), andNumber::forHumans()andabbreviate()no longer return-0for tiny negative values (#60736, #60768)- Fixes for
BelongsToMany::touch()when the related key is notid(#60708),TrustProxieswithat:*and multiple proxies (#60726), andprovidesTemporaryUploadUrls()on the S3 driver (#60755)
Upgrade Notes
No breaking changes are expected for typical applications. The image component's drivers depend on intervention/image (^4.0), which is a suggested package, so install it before using the Image facade. Review the changelog for PR-by-PR details when upgrading.
References