eyadhamza/laravel-webp
Laravel WebP is a package for converting images to WebP and serving optimized versions in Laravel applications. It helps reduce image file sizes, improve page load performance, and integrate WebP handling into your existing Laravel workflow with minimal setup.
Install and publish config
composer require eyadhamza/laravel-webp
php artisan vendor:publish --tag=laravel-webp
Check composer.json requirements first — the last release was December 2023, so confirm it satisfies your PHP/Laravel constraint before pulling it into a modern app.
Read the README facade/helper signature before writing code. This is a small package (19 stars), so the public surface is narrow. Locate the service provider registration, the facade/helper name, and any Artisan commands it registers with:
php artisan list | grep -i webp
First use case: convert an uploaded image to WebP on the way into storage.
use Eyadhamza\LaravelWebp\Facades\Webp;
$webpPath = Webp::convert($request->file('image'))
->quality(85)
->toStorage('public', 'images/hero.webp');
The exact method chain depends on the version — copy the shape from the README and adapt.
Sanity-check your runtime before assuming it works. WebP encoding needs GD compiled with WebP support or Imagick with the WebP delegate:
php -r "var_dump(function_exists('imagewebp'));"
php -r "var_dump(imagick::queryFormats('WEBP'));"
If both return false, the package has nothing to encode with — fix the PHP image extension before debugging the package.
Convert-and-store as an upload action. Wrap the package in a small action class so your controllers never depend on the facade directly. This makes it trivial to swap to intervention/image when you outgrow the package.
final class StoreWebpImage
{
public function __invoke(UploadedFile $file, string $disk = 'public'): string
{
$path = 'images/' . Str::uuid() . '.webp';
Webp::convert($file)->quality(82)->save($disk, $path);
return $path;
}
}
Keep the original alongside the WebP. Browsers without WebP support (older Safari on iOS ≤ 13, some email clients) still need a fallback. Store both and serve them via <picture>:
<picture>
<source srcset="{{ Storage::url("$path.webp") }}" type="image/webp">
<img src="{{ Storage::url("$path.jpg") }}" alt="{{ $alt }}">
</picture>
Queue the conversion for anything user-facing. WebP encoding of a 4 MB JPEG takes hundreds of milliseconds — do not do it inside the request that accepts the upload.
ProcessImageWebp::dispatch($image->id)->onQueue('images');
In the job, reload the model, resolve the file from the disk, convert, and write a webp_path column. Fire an event when done so the UI can poll or broadcast.
Middleware / route rewrite for content negotiation. If you already have legacy uploads, a middleware that checks Accept: image/webp and transparently swaps the response path is cheaper than retrofitting every model:
if (Str::contains($request>header('Accept'), 'image/webp') && $webp = $this->resolveWebp($path)) {
return response()->file(Storage::disk('public')->path($webp));
}
Gate it behind a config flag so you can turn it off per environment.
Bulk migration with an Artisan command. For existing media libraries, write a one-off command that iterates models, skips rows where webp_path is already set, and dispatches jobs in chunks. Make it idempotent (updateOrCreate, if (! $exists)) so re-runs are safe.
Blade component or directive. Expose a <x-webp-image :path="$path" :alt="$alt" /> component that emits the <picture> block above. Keeps fallback logic in one place instead of scattered across views.
composer require will tell you immediately, but also read the source before relying on it in production. If it's incompatible, fork it or copy the ~100 lines into your own app/Support directory; a WebP converter is not worth a dependency fight.quality(85) that looks fine under GD can look blocky under Imagick. Pin your driver in config and test with the same extension in staging as in production.imagealphablending/imagesavealpha; if it doesn't expose it, convert with Imagick directly.Content-Type: image/webp. Storage::response() sometimes guesses from extension — verify with curl -I that the header is right; a wrong MIME causes the browser to download rather than render.public or a configured disk. If it writes to storage/app/webp but your public disk symlink points elsewhere, images 404 in production while working locally. Check config/filesystems.php and the package config side by side.?v= query param derived from filemtime.deleting event to remove the derived WebP. Without this you accumulate orphans and your disk slowly fills with dead files.Storage::fake('public') in tests and assert the file exists with Storage::disk('public')->assertExists($path). Do not test actual encoding against real fixtures in unit tests — it's slow and driver-dependent; mock the facade instead.vendor/.image rule before handing them to the package; encoding a crafted file through GD has historically been an attack surface.How can I help you explore Laravel packages today?