NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages

Declare resized or reformatted image derivatives inline on a bucket — they are generated automatically whenever image media is added:

$this->addMediaBucket('photos')
    ->useDisk('public')
    ->storingVariantsOnDisk('cdn')               // variants can live on a different disk
    ->registerVariants(function (VariantRegistrar $v): void {
        $v->add('thumb')->fit('crop')->width(120)->height(120)->format('webp');
        $v->add('display')->width(800)->format('webp')->quality(80)->queued();
        $v->add('hero')->fit('cover')->width(1600)->height(600)->sharpen(2);
    });

Variant options

  • Sizing — width(), height() and fit() with contain, cover/crop, fill (padded with background()) and stretch.
  • Format — format() accepts jpg, png, webp, avif, gif (subject to the active driver).
  • Quality / look — quality(1..100), background('#ffffff'), sharpen(int).
  • Storage — storeOnDisk() for a per-variant disk override.
  • Execution — queued() / nonQueued() per variant.

Reading variants

$media = MediaLibrary::for($user)->first('photos');

$media->getUrl('thumb');             // public URL of the generated variant
$media->getPath('display');          // path on the variants disk
$media->diskFor('thumb');            // the disk the variant was written to
$media->hasGeneratedVariant('thumb');

Each generated variant is recorded in generated_variants with the file name, format and disk it was actually written to, and every URL, path, stream, move, copy and delete resolves that record:

$media->generated_variants;
// ['thumb' => ['file_name' => 'thumb.webp', 'format' => 'webp', 'disk' => 'hot']]

A variant without its own format() inherits the original’s (IMG_0001.JPG → keepformat.jpg; a format the drivers can’t write, such as .bmp, becomes jpg). The variant disk is resolved by precedence — the variant’s storeOnDisk(), the add’s storingVariantsOnDisk(), the bucket’s storingVariantsOnDisk(), config('media.variants_disk'), then the media’s own disk — and the disk each variant landed on is recorded, so getUrl(), diskFor() and deletes follow it.

Inspecting and regenerating

MediaLibrary::variants($media) lists the applicable definitions, shows what is generated or missing, and re-renders on demand — only missing variants unless you force it:

$variants = MediaLibrary::variants($media);

$variants->all();                                   // list<Variant> — every definition that applies
$variants->generated();                             // ['thumb']
$variants->missing();                               // ['display']
$variants->regenerate();                            // render the missing ones → ['display']
$variants->regenerate(only: ['thumb'], force: true); // re-render thumb even though it exists

MediaLibrary::regenerate($media, only: ['thumb'], force: true); // the same verb, flat

Drivers, queueing & events

Variants use ext-imagick by default and fall back to ext-gd; both auto-orient EXIF-rotated photos. They generate synchronously by default — opt into the queue per variant (->queued()), per add (->onQueue('media')) or globally via config('media.queue_variants_by_default'). VariantHasBeenGenerated fires per variant and VariantsHaveBeenGenerated once an add's variants are all done. getUrl() only knows the variants a bucket declares: asking for a name the bucket never defined, or one not generated yet, throws InvalidVariant unless config('media.url_fallback_to_original') is true — guard optional names with hasGeneratedVariant().

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.