Laravel News published a tutorial explaining the first-party image processing API shipped in Laravel 13.20 through the Illuminate\Image component, introduced in Pull Request #59276 on the laravel/framework repository. Before this, developers relied on third-party packages for tasks like resizing avatars or converting uploads to WebP.

The GD and Imagick drivers are powered by Intervention Image v4, installed separately with `composer require intervention/image:^4.0`. GD is the default driver; Imagick can be set globally via the `images.default` config or per call using `usingImagick()` or `using('imagick')`. Calling a driver without Intervention Image installed throws an `ImageException` naming the missing package.

Images can be loaded from many sources: `$request->image('avatar')` for uploads (returns null if the field is missing or not a file), or via the `Image` facade methods `fromPath()`, `fromUrl()`, `fromStorage()`, `fromBytes()`, and `fromBase64()`, plus `Storage::disk('s3')->image('path')` as a shortcut for `fromStorage()`.

Every transformation returns a new immutable `Image` instance, and nothing is processed until output is requested via methods like `store()`, `toBytes()`, or `width()`. This allows building a base image (e.g. calling `orient()` to auto-rotate based on EXIF data) and branching into multiple variants, such as a thumbnail via `cover(300, 300)` and a display size via `scale(width: 1600)`, without either branch affecting the other.

Five resizing methods are available: `cover($w, $h)` crops to fill exact dimensions (ideal for avatars); `contain($w, $h, $background)` fits the image inside the box with optional padding; `scale($w, $h)` resizes proportionally and never upscales (mapped internally to Intervention's `scaleDown()`); `resize($w, $h)` forces exact dimensions and may distort; `crop($w, $h, $x, $y)` cuts a region at a given offset.

Other adjustments include `rotate()`, `blur()` (0–100, default 5), `sharpen()` (0–100, default 10), `grayscale()`, `flip()`/`flipVertically()`, and `flop()`/`flipHorizontally()`. Format conversion methods `toWebp()`, `toJpg()`, `toPng()`, `toGif()`, `toAvif()`, and `toBmp()` are paired with a `quality()` setting (1–100) for lossy formats. The shortcut `optimize()` converts to WebP at quality 70 by default, or accepts custom format and quality, e.g. `optimize('avif', 60)`. Supported input formats are JPEG, PNG, GIF, BMP, and WebP.

Storage mirrors Laravel's UploadedFile API: `store()`, `storeAs()`, and `storePublicly()` write to a configured disk, automatically applying the correct file extension for the output format. For raw data, `toBytes()`, `toBase64()`, and `toDataUri()` are available, plus inspection methods `width()`, `height()`, `dimensions()`, `mimeType()`, and `extension()`.

An example avatar controller validates an upload, then chains `orient()->cover(512, 512)->optimize()->storePublicly('avatars', disk: 's3')`. A responsive-variant example loops over widths 480, 960, and 1440, using `scale(width: $width)` — since `scale()` never upscales, a 900px original stays at 900px even in the 1440 iteration.

The `Image` class uses Laravel's `Conditionable` trait, so `when()` and `unless()` can apply transformations conditionally. Extension points include `Image::extend()` for registering a custom driver and `Image::transformUsing()` for overriding a specific transformation per driver, as well as implementing the `Illuminate\Contracts\Image\Transformation` contract for fully custom transformations.

Caveats noted: Image instances cannot be serialized, so passing one to a queued job throws an `ImageException` (store the file and pass its path instead); processing is lazy and cached after first output; and failures such as unsupported or undecodable input throw `ImageException` uniformly.