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

Webp Convert Laravel Package

rosell-dk/webp-convert

PHP library for converting JPEG, PNG and other images to WebP. It tries multiple converters (cwebp, vips, Imagick, GD, ffmpeg, cloud APIs) with automatic fallback, so conversions succeed even on limited hosts. Includes quality options, error handling and framework integrations.

Deep Wiki
Context7

Getting Started

Install

composer require rosell-dk/webp-convert

Nothing else is required to boot it — the library auto-detects which conversion methods are usable on the host at call time, so an install on a host with only GD still "works", just with worse output.

Where to look first

  1. README.md — the two entry points you actually use (WebPConvert::convert() for one-shot conversion, ServeConvertedWebP::serve() for on-the-fly serving).
  2. docs/options.md — the full option list (converters, quality, max-quality, metadata, fail, logger, alpha/near-lossless).
  3. docs/converters.md — the per-converter quirks table; skim it once, it explains 90% of the "why did this host produce a worse file" questions.
  4. src/Serve/ — the serving layer, if you plan to convert lazily instead of at upload time.

Minimal working call

use WebPConvert\WebPConvert;

WebPConvert::convert($sourceJpeg, $targetWebp, [
    'converters' => ['cwebp', 'vips', 'imagick', 'gd'],
    'quality'    => 82,
]);

It throws WebPConvert\Convert\Exceptions\ConversionFailedException when every converter in the list fails. Catch it — a failed WebP conversion should never fail the surrounding upload/request.

First use case: convert on upload

For a typical Laravel app this is the lowest-risk integration: convert once when the file arrives, store the sibling .webp on the same disk, and forget about it. No request-time cost, no Accept header negotiation, no cache invalidation logic. Reach for the Serve namespace only when you genuinely cannot pre-generate (user-supplied remote images, very large libraries you haven't backfilled).

Diagnostic script — run this on every new host before trusting the package

$report = WebPConvert::convert($src, $dst, [
    'converters' => ['cwebp', 'vips', 'imagick', 'gd'],
    'fail'       => 'report', // returns the report instead of throwing
]);

// then separately sanity-check the underlying capabilities
var_dump(gd_info()['WebP Support'] ?? false);
var_dump(Imagick::queryFormats('WEBP'));
echo shell_exec('which cwebp vips convert gm');

That output tells you in 30 seconds whether you're on a "cwebp, quality 82, 40 KB" host or a "GD only, quality 82, 90 KB, slightly different colour" host — which determines whether you can promise your product team a file-size win.

Implementation Patterns

1. Fixed converter order, not auto-detection

The default order is sensible, but auto-detection makes behaviour host-dependent: staging (with cwebp) and production (without) can silently produce different bytes for the same upload. Always pin the list explicitly and keep it in one config location:

// config/webp.php
return [
    'converters'  => ['cwebp', 'vips', 'imagick', 'gd'],
    'quality'     => env('WEBP_QUALITY', 82),
    'max-quality' => 90,
    'metadata'    => 'none',
];

Put cheap, high-quality binaries first (cwebp and vips beat everything else on file size), extensions last as a safety net.

2. Wrap it once, never call it from controllers directly

A thin service keeps option drift out of your codebase and gives you one place to log, time, and fall back:

class WebpConverter
{
    public function convert(string $source, string $destination): bool
    {
        try {
            WebPConvert::convert($source, $destination, config('webp'));
            return true;
        } catch (ConversionFailedException $e) {
            Log::warning('webp conversion failed', [
                'source' => $source,
                'report' => $e->getConversionErrorMessages(),
            ]);
            return false;
        }
    }
}

Bind it in a service provider, inject it where needed. Everything downstream stops caring which converter ran.

3. Upload pipeline pattern (recommended default)

$path = $request->file('image')->store('uploads', 'public');
$abs  = Storage::disk('public')->path($path);
$webp = preg_replace('/\.[^.]+$/', '.webp', $abs);

if ($converter->convert($abs, $webp)) {
    $model->update(['webp_path' => str_replace($abs, '', $webp)]);
}

Store the relation in the DB so you can find unconverted records later. This gives you a free backfill queue: whereNull('webp_path') is your migration worklist when you change quality settings.

4. Queue the heavy work, but convert on the request for small images

Conversions are I/O- and CPU-bound: cwebp on a 4 MB JPEG is ~100–300 ms, GD on a 12 MP PNG can be over a second and a lot of RAM. Pattern that works well:

  • Avatar/thumbnail uploads → inline, they're small and users expect instant feedback.
  • Anything over ~2 MB or from a batch/import → dispatch a job.
  • Backfills → chunked job, one image per job, rate-limited.

5. On-demand serving with ServeConvertedWebP

When you can't pre-generate, the package's serving layer handles the Accept: image/webp check, serves the cached file if fresh, converts if not, and falls back to the original on failure:

use WebPConvert\Serve\ServeConvertedWebP;

ServeConvertedWebP::serve($originalPath, $webpCachePath, [
    'convert'   => ['quality' => 80, 'converters' => ['cwebp', 'vips', 'gd']],
    'reconvert' => 30 * 24 * 3600,   // seconds before the cached webp is rebuilt
]);

Two operational notes: it emits the Content-Type and Vary: Accept headers for you, and it must sit behind a path that your front controller actually routes to PHP — a static-file nginx rule will bypass it entirely.

6. Quality strategy

Don't ship one global quality. Reasonable starting points:

Use case Quality Notes
Thumbnails / list images 70–75 File size dominates
Content images 80–82 Sweet spot for photos
Hero / full-bleed 85–88 max-quality caps accidental 100s
PNG screenshots / UI 75–80 + 'near-lossless' => 60 Preserves flat-colour edges

Set max-quality in config so a per-call override can't quietly triple your CDN bill.

7. Framework glue

In Laravel, a webp:backfill artisan command that walks records in chunks and re-runs the converter is the highest-value 40 lines you'll write with this package. In Symfony, the equivalent is a console command. Either way, make it idempotent: skip when the destination exists and is newer than the source, so re-runs are cheap.

Gotchas and Tips

Binaries are the #1 source of "works locally, fails in prod"

  • PHP-FPM often has a minimal PATH, and which isn't available. If cwebp lives at /usr/local/bin/cwebp, set the binary path explicitly in the converter options rather than hoping for discovery.
  • exec/shell_exec are frequently in disable_functions on shared hosting. The library degrades gracefully to GD, but you'll see a large size regression with no error — which is why you pin the converter list and log which one succeeded.
  • Paths containing spaces need quoting; the library handles this, but your own shell_exec diagnostics won't.

Extension support is not a given

imagick being installed does not mean ImageMagick was compiled with the WebP delegate — many distroless and Alpine images aren't. Same for GD: gd_info()['WebP Support'] must be true for imagewebp() to exist at all. Verify both on every environment; don't infer from php -m.

GraphicsMagick is the most likely to be installed and least likely to write WebP — treat it as a last resort, not a fallback.

Metadata default is 'none' — and that includes EXIF orientation

If your upload pipeline relies on the browser to honour EXIF rotation, stripping metadata before you autorotate gives you sideways images. Normalise orientation before calling the converter (Intervention, Imagick autoOrient(), or exif_read_data), then convert. Keep 'metadata' => 'icc' for photography/print where colour accuracy matters; 'none' for everything else, as it saves a few KB per file.

GD silently degrades PNG alpha

Palette PNGs with transparency can come out with black or flat backgrounds under GD depending on version. If you support PNG uploads, either force imagick for them or set 'alpha-quality' explicitly and eyeball a test asset. This is the classic "it looked fine in the smoke test" bug.

Failure handling: fail changes the contract

Default is 'exception'. Setting 'fail' => 'report' makes the call return a structured report instead of throwing — ideal for diagnostics and batch jobs where you want to collect failures rather than unwind a loop with try/catch per item. Don't mix the two modes in one codebase; pick one per call site and be explicit.

Concurrency and partial writes

Two requests converting the same source simultaneously can both write the destination. cwebp exits non-zero on a partial write, but GD may leave a truncated file. Either write to a unique temp name and rename() atomically, or gate with a lock/cache key. This bites hardest on the first traffic spike after a cache purge.

Don't reconvert on every request

The most common performance mistake with ServeConvertedWebP is omitting 'reconvert', or setting it very low. Every miss shells out to cwebp. Set a real TTL and check file mtime first; the cost of a stale WebP is zero.

Quality above ~90 is wasted bytes

WebP's curve flattens hard after 85–88. Pushing to 95 typically costs 30–50% more bytes for a difference nobody can see at display sizes. Cap it with max-quality.

Large images and memory

GD decodes the full bitmap; a 6000×4000 PNG can need 100 MB+. If you're on a 128 MB memory_limit, GD will fatal, not throw. Either bump the limit for the job worker or drop GD from the list for large uploads and let cwebp (which streams) handle them.

Animated images aren't handled

Animated GIF/WebP and APNG pass through unconverted or fail quietly depending on the converter. Detect and skip, or use a dedicated toolchain.

Observability

The library accepts a PSR-3 logger. Wire it — "which converter ran, how long did it take, what was the output size" is exactly the telemetry you'll want when someone asks why images got slower after the last deploy. Without it you're grepping for exceptions that never fire.

Testing

Inject the converter service and fake it in feature tests; don't run real conversions in CI. Keep one integration test that runs a single real conversion and asserts the output is a valid WebP (getimagesize() reports IMAGETYPE_WEBP) so you catch a broken host before users do.

Related packages worth knowing

rosell-dk/dom-util-for-webp for rewriting <img> tags to <picture>, and rosell-dk/webp-convert-cloud-service if you'd rather offload conversions than fight binaries on shared hosting.

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.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle