Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Image Laravel Laravel Package

intervention/image-laravel

Laravel integration for Intervention Image that provides a service provider, facade, and publishable configuration file, letting you set the image driver and options once centrally instead of repeating them on every image manager call.

View on GitHub
Deep Wiki
Context7

Getting Started

This package is a thin but genuinely useful glue layer: it registers a singleton ImageManager, a facade, a publishable config file, and a response()->image() macro. Almost all real work is done by intervention/image itself, so treat this repo as "wiring + config" and the Intervention Image v4 docs as your actual API reference.

Install and publish

composer require intervention/image-laravel
php artisan vendor:publish --provider="Intervention\Image\Laravel\ServiceProvider"

That drops config/intervention-image.php. Do not edit a config/image.php unless it already exists from an older install — v4.1.0 renamed the file to avoid a collision with Laravel 13.20's own image integration (the old name is still detected via a fallback, but it's deprecated behaviour).

First place to look

Open the published config first. It tells you everything about how the manager will behave before you write a single line of image code:

return [
    'driver' => env('IMAGE_DRIVER', \Intervention\Image\Drivers\Gd\Driver::class),

    'options' => [
        'autoOrientation' => true,   // honour EXIF rotation on decode
        'decodeAnimation' => true,   // keep GIF/WebP animation frames
        'backgroundColor' => 'ffffff',
        'strip' => false,            // drop EXIF/metadata on encode
    ],
];

Then verify the driver actually exists in your runtime — GD is bundled with most PHP builds, Imagick needs ext-imagick, and Vips needs ext-vips. A missing extension is the #1 cause of "it works locally, dies in production".

First use case: resize an upload and store it

The canonical "day one" task, using the facade:

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Intervention\Image\Laravel\Facades\Image;

Route::post('/avatar', function (Request $request) {
    $upload = $request->file('avatar');

    $image = Image::decode($upload)->cover(400, 400);

    Storage::disk('public')->put(
        'avatars/' . Str::uuid() . '.' . $upload->getClientOriginalExtension(),
        $image->encodeUsingFileExtension($upload->getClientOriginalExtension(), quality: 85)
    );
});

For an UploadedFile specifically, decodeSplFileInfo($request->file('image')) is the more explicit form and avoids any ambiguity about what decode() will sniff.

Implementation Patterns

Pattern 1 — Pick one access style and stick to it

There are three equivalent entry points; mixing them in one codebase causes confusion during review:

Style Use when
Image::decode(...) facade Controllers, routes, one-off scripts. Zero ceremony.
ImageManagerInterface injected Services, actions, jobs — testable, swappable, no static coupling.
app('intervention.image') Legacy/container access only.

Dependency injection is the cleanest for anything you intend to unit test:

use Intervention\Image\Interfaces\ImageManagerInterface;

class StoreThumbnail
{
    public function __construct(
        private ImageManagerInterface $manager,
        private Filesystem $storage,
    ) {}

    public function handle(string $path): string
    {
        $image = $this->manager->decode($this->storage->get($path))->scale(width: 800);

        $out = 'thumbs/' . basename($path);
        $this->storage->put($out, $image->encodeUsingFileExtension('jpg', quality: 75));

        return $out;
    }
}

Both ImageManager::class and ImageManagerInterface::class are bound as configured singletons (since 4.0.1), so DI gives you the same instance the facade uses — config changes are picked up identically either way.

Pattern 2 — A reusable transformation pipeline

Rather than sprinkling resize() calls everywhere, centralise the variant matrix. This is the pattern that pays for itself once you have more than one image size:

class ImagePipeline
{
    public function __construct(private ImageManagerInterface $manager) {}

    public function variant(string $source, string $variant): string
    {
        $image = $this->manager->decode($source);

        $image = match ($variant) {
            'thumb'   => $image->cover(200, 200),
            'card'    => $image->cover(640, 360),
            'hero'    => $image->scale(width: 1920),
            default   => throw new InvalidArgumentException("Unknown variant [$variant]"),
        };

        return (string) $image->encodeUsingFileExtension('webp', quality: 80);
    }
}

Because backgroundColor and strip come from config, a cover() on a transparent PNG will get the configured background colour automatically — set it once, not per call.

Pattern 3 — Return images straight to the browser

The response macro removes the encode-then-response()->make() dance:

use Intervention\Image\Format;
use Intervention\Image\Laravel\Facades\Image;
use Illuminate\Support\Facades\Storage;

Route::get('/og/{slug}', function (string $slug) {
    $image = Image::decode(Storage::get("og/{$slug}.jpg"))->scale(width: 1200);

    return response()->image($image, Format::WEBP, quality: 65);
});

Only the first argument is required — skip Format:: and you get the image's own format. Add cache headers with a normal ->header() chain; the macro returns a regular response instance.

Pattern 4 — Push heavy work to queues

Decoding a 12 MP JPEG costs tens of megabytes of RAM. In a request cycle that's a timeout waiting to happen; in a jobs table row it's fine:

class GenerateVariants implements ShouldQueue
{
    use Queueable;

    public int $timeout = 120;

    public function __construct(public string $path) {}

    public function handle(ImageManagerInterface $manager, ImagePipeline $pipeline): void
    {
        $raw = Storage::disk('s3')->get($this->path);

        foreach (['thumb', 'card', 'hero'] as $variant) {
            Storage::disk('s3')->put(
                "derived/{$variant}/" . basename($this->path),
                $pipeline->variant($raw, $variant)
            );
        }
    }
}

Note: read the file into memory once and reuse it ($raw) rather than re-fetching per variant.

Pattern 5 — Integration points worth knowing

  • Storage: always pass a string or stream to put(); encodeUsingFileExtension('png') returns an encoded image object, not a string — cast with (string) or let Storage handle it. Never mix the extension argument with a mismatched destination filename.
  • Validation: keep your image|mimes:jpg,png,webp|max:5120 rules. This package does not validate uploads; decode() will happily throw on a corrupt file.
  • Filament / Nova / custom form fields: inject the manager interface into the field's service rather than calling the facade from within view code.
  • Testing: Storage::fake('public') + the facade works fine in tests; the manager singleton is resolved from the container so config overrides via config(['intervention-image.driver' => ...]) in setUp() are honoured.

Gotchas and Tips

The v3 → v4 API rename will bite you

The README itself is inconsistent across versions because the underlying library renamed methods. If you're on intervention/image v4 (this package's 4.x line), use:

  • Image::decode($data) — not Image::read() (that was v1.x of this package / v3 of the core)
  • encodeUsingFileExtension() / encodeUsingFormat() — check whichever your core version exposes
  • Format::WEBP enum — not the string 'webp'

Copying snippets from blog posts written before mid-2024 is the most common source of BadMethodCallException.

The Laravel 13.20 facade collision

Laravel shipped its own Intervention-based image integration in 13.20, and it registers a facade alias image and a config/image.php. If you're on 13.20+:

  • Use app('intervention.image'), not app('image')
  • Ensure only config/intervention-image.php exists; the old config/image.php is read as a fallback but will silently lose to Laravel's own config in confusing ways
  • Upgrade to >=4.1.0 — 4.1.1 additionally fixed wrong config keys, so pin "intervention/image-laravel": "^4.1.1"

This is a young, low-star package with a small surface area; pin minor versions and read release notes before composer update.

Config keys that silently do nothing

Because the driver is constructed from the options array at container-resolution time, malformed keys are ignored rather than throwing. If autoOrientation appears to have no effect:

  1. Confirm the key name matches exactly (autoOrientation, decodeAnimation, backgroundColor, strip)
  2. Run php artisan config:clear — an opcache'd config cache is the usual culprit
  3. Check the source image actually carries an EXIF Orientation tag; GD strips it on some decode paths

Also remember 'strip' => false is the default: if you care about privacy (GPS coordinates in phone photos), set it to true — this is a one-line, high-impact change.

The response macro is HTTP-only

It's registered lazily when ResponseFactory is resolved, and is deliberately disabled in console context (since 1.5.3, refined in 1.5.6). Calling response()->image() from a queued job, an artisan command, or a phpunit assertion that hits the console kernel will fail. Encode manually in those contexts:

$bytes = (string) $image->encodeUsingFileExtension('jpg', quality: 70);

Memory and driver pitfalls

  • GD is the safe default but loses quality on large downscales and has weaker colour management. Switch to Imagick for CMYK source files, ICC profiles, or serious print work.
  • decodeAnimation defaults to true — decoding a 200-frame GIF into memory is a denial-of-service vector on user uploads. Set IMAGE_DRIVER-adjacent config or override per call for untrusted input, and cap dimensions before processing.
  • Set memory_limit and consider ini_set('memory_limit', ...) scoped around the job; GD needs roughly width × height × 4 bytes plus headroom.
  • Singleton caveat: the manager is a singleton, so per-request runtime config changes after resolution won't apply. Build a new manager with new ImageManager(new Driver(...)) for occasional one-off drivers rather than mutating config mid-request.

Extension points

There is very little to extend here — no custom drivers, no middleware, no events. Your extension surface is:

  1. The image.intervention container binding — rebind it in a service provider if you need a differently-configured manager (e.g. a strip: true instance for public uploads alongside the default).
  2. Your own action/job classes wrapping the manager — this is where all the actual reuse lives.
  3. The published config file — the intended customisation point, and the only one the maintainer supports.

If you find yourself wanting more than this, you want the features in intervention/image itself, not the Laravel wrapper.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
muka/shape-reader
tdt/json
refinery29/php-cs-fixer-config
hotmeteor/eco-env
mvaliolahi/hydrate
wearede/tbcpay-php
parsnick/steak
thibaud-dauce/mattermost-php
multilinguals/apollo-client
antaresbr/collection
kr0lik/phpstan-rules
loco/loco
starfederation/datastar-php
yupmin/modern-phystrix
stephenjude/extended-artisan-commands
lumphp/laravel-macroable
lumphp/laravel-collections
lumphp/laravel-contracts
sermepa/sermepa
arkecosystem/fortify