spatie-medialibrary-webp-downloader maintained by x-laravel
x-laravel/spatie-medialibrary-webp-downloader
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.