Laravel's first-party image API covered the write path since 13.20 — upload, transform, store on a disk — but serving a resized image over HTTP required boilerplate: calling toBytes(), building a response, and setting the content type manually. Laravel 13.25 fixes the read path with three additions.
First, Image now implements the Responsable contract, making an image instance a valid return value from a route or controller. The framework calls toResponse(), runs the transformation pipeline lazily, returns the bytes with a 200, and sets Content-Type from the produced output — a stored JPEG returned as WebP correctly yields image/webp. Because the instance is lazy, the transformation only runs when bytes are actually requested.
The default response carries no cache headers. Calling toResponse($request) yourself returns an Illuminate\Http\Response, so setMaxAge(), setPublic(), setEtag() and the rest of the response API are available. For real traffic, the article recommends generating the derived file once and serving it from disk afterwards, embedding the model's updated_at timestamp in the filename so updated photos get new paths without cache invalidation.
Second, toFormat() is now public and accepts webp, jpg, jpeg, png, gif, avif, heic, heif (normalized to heic) and bmp directly — replacing match statements for request-driven formats. Invalid values throw an ImageException (a 500, not 404), so route constraints or validation are advised. This makes AVIF-with-fallback endpoints paired with the <picture> element short to write.
Third, Image::fromStream() builds an instance from a stream resource, e.g. Storage::disk('s3')->readStream($path). The read is lazy; an empty stream throws "Invalid stream image data." when the pipeline runs. It complements fromPath(), fromStorage(), fromUpload(), fromUrl(), fromBytes() and fromBase64() for handles like php://input or files read from archives.
The article closes with a complete endpoint: validated width (bounded 32–2000) and format, streaming from S3, scaling, converting, quality 80, and a one-year public cache — protected by a signed route to prevent arbitrary variant generation against your storage bill. All three changes were contributed by Caleb White in framework PRs #61111, #61109 and #61110.
Comments
No comments yet — be the first.
Open the discussion
No account or password needed — just enter your e-mail and we’ll send you a one-time sign-in link. First time here? You’re set up automatically.
Your rating will be applied automatically after you sign in.
Check your inbox
We’ve sent a sign-in link to …. Open it on this device — this tab will sign you in automatically.
Nothing arrived? Check your spam folder — and mark the mail as "Not spam" so it lands in your inbox next time.