x-laravel / spatie-medialibrary-webp-downloader
Drop-in Downloader for spatie/laravel-medialibrary that converts every fetched image to WebP on the fly.
Package info
github.com/x-laravel/spatie-medialibrary-webp-downloader
pkg:composer/x-laravel/spatie-medialibrary-webp-downloader
Requires
- php: ^8.3
- illuminate/support: ^12.0|^13.0
- spatie/image: ^3.0
- spatie/laravel-medialibrary: ^11.0|^12.0
Requires (Dev)
- orchestra/testbench: ^10.0|^11.0
- phpunit/phpunit: ^11.0|^12.0
This package is auto-updated.
Last update: 2026-05-10 21:38:09 UTC
README
Drop-in Downloader replacement for spatie/laravel-medialibrary that converts every remote image into WebP on the fly while it is being downloaded.
Unofficial plugin. Not affiliated with Spatie.
How It Works
- Implements Spatie's
Downloaderinterface — registers as themedia_downloaderinconfig/media-library.php - Inherits the SSL & User-Agent behaviour of
DefaultDownloader, then re-encodes the temp file as WebP usingspatie/image(the same library media-library uses internally) - Reads Spatie's existing
media-library.image_driverconfig (gd,imagick, orvips) — no extra wiring required, no extra dependencies pulled in - Non-image payloads (PDF, video, archives) and configured skip MIME types pass through untouched
Requirements
- PHP ^8.3
- Laravel ^12.0 | ^13.0
spatie/laravel-medialibrary ^11.0 | ^12.0spatie/image ^3.0(already installed transitively by media-library)- The PHP extension matching
media-library.image_driver—ext-gd,ext-imagick, or libvips forvips
Installation
composer require x-laravel/spatie-medialibrary-webp-downloader
There is no service provider — wiring happens through Spatie's own config.
Setup
Edit config/media-library.php and swap the downloader binding:
use XLaravel\Spatie\MediaLibrary\WebpDownloader\WebpDownloader; return [ // ... 'media_downloader' => WebpDownloader::class, // ... ];
That's it. From now on every addMediaFromUrl() call passes through the WebP converter.
Image Driver
The driver is chosen automatically from Spatie's existing config and forwarded to Spatie\Image\Image::useImageDriver():
// config/media-library.php 'image_driver' => env('IMAGE_DRIVER', 'gd'), // 'gd', 'imagick', or 'vips'
The required PHP extension (ext-gd / ext-imagick) — or libvips for vips — must be loaded.
Tuning
WebpDownloader accepts two optional constructor arguments — all auto-resolved via the container with sensible defaults:
| Argument | Default | Meaning |
|---|---|---|
quality |
85 |
WebP quality (0–100) |
skipMimes |
['image/svg+xml', 'image/gif'] |
MIME types that bypass conversion |
To override, bind a custom instance in your own service provider:
use XLaravel\Spatie\MediaLibrary\WebpDownloader\WebpDownloader; $this->app->bind(WebpDownloader::class, fn () => new WebpDownloader( quality: 90, skipMimes: ['image/svg+xml'], ));
Automatic File Name Correction
WebpDownloaderServiceProvider is auto-discovered. It hooks Media::creating and rewrites the file_name extension to .webp when all of the following hold:
- The configured
media-library.media_downloaderisWebpDownloader(or a subclass) - The persisted
mime_typeisimage/webp - The current
file_namedoes not already end with.webp(case-insensitive)
So obama.jpg → obama.webp (basename preserved, extension swapped). Disk uploads of unrelated formats and media added while a different downloader is configured are never touched.
If you want to opt out, remove the provider from config/app.php (or set extra.laravel.dont-discover in your root composer.json) and rename manually with ->usingFileName(...).
Bulk Converting Existing Media
For repositories that already have thousands of JPEGs/PNGs sitting on disk, the package ships an Artisan command that walks the media table and re-encodes them in place:
# Dry run — shows which rows would be converted, touches nothing php artisan media-library:webp-convert --dry-run # Convert everything php artisan media-library:webp-convert # Scope to a collection or model php artisan media-library:webp-convert --collection=images php artisan media-library:webp-convert --model="App\Models\Post" # Tune chunk size for large tables php artisan media-library:webp-convert --chunk=500 # Dispatch each row as a queued job (recommended for large libraries) php artisan media-library:webp-convert --queue php artisan media-library:webp-convert --queue --queue-connection=redis --queue-name=media # Incremental migration — only media added after a date php artisan media-library:webp-convert --since=2026-04-01 # Parallel workers splitting the id range # Terminal A: php artisan media-library:webp-convert --id-from=1 --id-to=50000 --queue # Terminal B: php artisan media-library:webp-convert --id-from=50001 --id-to=100000 --queue
For each Media row the command:
- Skips
image/webp, non-image MIME types, and entries in the configuredWebpDownloaderskipMimes - Reads the file via the row's own
diskand thePathGeneratorresolved by Spatie (custom path generators are honoured automatically) - Re-encodes to WebP using
media-library.image_driverand the quality from the container-boundWebpDownloaderinstance - Writes the new
.webpfile, deletes the original, and updatesfile_name,mime_type, andsizeon the Media row viasaveQuietly()so the auto-rename listener doesn't fire on data we have already corrected
Existing conversions (thumbnails, responsive images) become stale because their source has changed. Run php artisan media-library:regenerate afterwards — the convert command prints a reminder when it finishes.
Testing
# Build first (once per PHP version) DOCKER_BUILDKIT=0 docker compose --profile php83 build # Run tests docker compose --profile php83 up docker compose --profile php84 up docker compose --profile php85 up
License
This package is open-sourced software licensed under the MIT license.