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

Laravel Image Variants Laravel Package

fruitcake/laravel-image-variants

A Laravel package for defining and generating image variants, such as thumbnails and responsive sizes, and exposing them through simple URLs or srcsets. Streamlines image processing and delivery in Laravel applications.

View on GitHub
Deep Wiki
Context7

Getting Started

fruitcake/laravel-image-variants generates derivative images (thumbnails, crops, responsive widths, format conversions) from a single source image on demand, caching the output so subsequent requests are served straight from disk or CDN.

Install and publish

composer require fruitcake/laravel-image-variants
php artisan vendor:publish --tag="image-variants-config"

Version floor (new in v0.2.2): the package now requires Laravel >= 13.25.0 because format conversion is delegated to Laravel's native toFormat() instead of the package's previous internal handling. If your composer.json pins laravel/framework below that, the upgrade will either fail resolution or silently lose the format option — check composer why laravel/framework before rolling this out across older apps.

Where to look first

  1. config/image-variants.php — this is the whole API surface in practice: source disk, cache disk, route prefix, and the variants map.
  2. The generated route (/img/{path}/{variant} by default) — every variant is just a URL, which is why it plays nicely with srcset and CDN caching.
  3. The Blade component / URL helper the package registers — use it rather than hand-building URLs so path encoding and cache-busting stay consistent.

First use case — a product or article listing that needs a 300×300 crop plus a full-size fallback:

// config/image-variants.php
'variants' => [
    'thumb' => ['w' => 300, 'h' => 300, 'fit' => 'crop'],
    'hero'  => ['w' => 1600, 'format' => 'webp', 'q' => 80],
],

Then render with the package helper/component in Blade and let the first request for each variant do the work. Start with one variant and one page; confirm the file lands on the cache disk before wiring it into templates broadly.

Implementation Patterns

Define variants once, reference them by name. Treat the variant name as a contract shared between config, templates, and tests. Scattering ['w' => 300, 'h' => 300, 'fit' => 'crop'] literals across views is the fastest way to end up with an unbounded cache.

Responsive images are the sweet spot. Because variants are URL-addressable, srcset is nearly free:

<img
    src="{{ image_variant($post->image, 'hero') }}"
    srcset="{{ image_variant($post->image, 'thumb') }} 300w,
            {{ image_variant($post->image, 'hero') }} 1600w"
    sizes="(max-width: 600px) 300px, 1600px"
    alt="{{ $post->title }}"
>

Generate ahead of traffic, not during it. First-hit generation puts image decoding inside a web request. For anything user-facing at scale, drive variant creation from a queued job on model save (or a backfill command) so the HTTP path is a cache hit. Combine with ->onQueue('images') and a worker with a memory limit high enough for your largest source images — GD/Imagick blowups here are a classic cause of silent job failures.

Invalidate on update, not on read. When a model's image attribute changes, delete the old derivative paths; otherwise the URL doesn't change and the stale variant is served forever. A small Observer calling a forgetVariants($path) helper covers 95% of cases.

Keep sources and derivatives on separate disks. Source images on a private/local disk, variants on a public or S3 disk fronted by a CDN. This keeps originals unguessable and lets you set far-future cache headers on derivatives only.

Test against the fake filesystem. Storage::fake() for both disks plus a request to the variant route is a fast, deterministic check that your variant definitions produce output — much better than asserting on binary content.

Format negotiation. With v0.2.2, 'format' => 'webp' (or avif) goes through Laravel's toFormat(). Prefer emitting webp/avif variants alongside a jpeg fallback rather than replacing it, and only adopt avif after confirming the worker's driver actually supports it (see Gotchas).

Gotchas and Tips

Cache keys usually don't include your variant parameters. Changing 'thumb' => ['w' => 300] to 400 does not retroactively change the URL on most setups — existing files keep serving. After any variant config edit, flush the variant disk (or bump a version segment in the route prefix) as part of the deploy. Make this a documented deploy step, not tribal knowledge.

Driver capability is the most common surprise. avif in particular requires a recent ImageMagick/Imagick build; GD support is spottier. A variant that renders fine as jpeg and produces a zero-byte or 500 response as avif is a driver problem, not a package problem. Verify with a one-off php artisan tinker conversion against your production image before adding the format to config.

The v0.2.2 toFormat() dependency is a real deployment constraint. Because the format path now runs through Laravel itself, behaviour on 13.25.0 versus later patch releases may differ slightly (quality defaults, alpha handling). Pin your framework version deliberately and re-test your webp variants — not just your thumbnails — after any Laravel bump. On apps locked to an older major, stay on v0.2.1 and skip the format option.

Signed URLs / auth. If sources live on a private disk, the variant route needs its own authorization story. Don't rely on the URL being obscure; add middleware or a signed-route guard if the images aren't public.

CDN caching fights invalidation. Once a derivative is cached at the edge, deleting it from your disk doesn't evict it. Include a content hash (or the model's updated_at) in the variant path if you need updates to propagate quickly, otherwise accept eventual consistency and purge explicitly.

Debugging checklist for a broken variant: (1) does the source path resolve on the source disk? (2) is the variant name actually in config('image-variants.variants')? (3) does the cache disk have write permission / correct visibility? (4) is the worker's memory_limit above the source image's decoded size? (5) does the requested format work with the current driver? Work down that list before touching package code.

Extension points worth knowing. Custom drivers (swap the underlying image library), the config's disk/route settings, and a srcset-style helper for generating width ladders are the natural seams. If you need per-tenant variant sets, resolve config at runtime in a service provider rather than maintaining multiple config files — but remember the invalidation rules above apply per tenant.

Small operational habit that pays off: log variant generation (path, variant, duration, output size) at debug level. It's the only reliable way to spot a template that's quietly generating thousands of one-off sizes in production.

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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin