WebP is typically 25-35% smaller than JPEG at equivalent quality. PHP can create WebP natively via GD (if compiled with libwebp support, which most distributions include) or via Imagick. The main gotcha: GD's imagewebp() quality parameter has a different scale than imagejpeg() — 80 in WebP roughly matches 90 in JPEG.
Method 1: GD (built-in, fastest)
GD is almost universally available in PHP. Check that WebP support is compiled in before using it.
For on-the-fly serving with browser detection, check the Accept header:
Method 2: Imagick (more control, metadata handling)
Imagick gives finer control — lossless mode, metadata stripping, and progressive encoding.
stripImage();
$imagick->setImageFormat('webp');
$imagick->setImageCompressionQuality($quality);
if ($lossless) {
// Lossless WebP (larger but pixel-perfect)
$imagick->setOption('webp:lossless', 'true');
} else {
// Lossy — these tune the internal WebP encoder
$imagick->setOption('webp:method', '6'); // slowest=6, fastest=0
$imagick->setOption('webp:alpha-quality', '90');
}
$imagick->writeImage($outPath);
$imagick->clear();
}
jpgToWebpImagick('photo.jpg', 'photo.webp', 80);
jpgToWebpImagick('logo.jpg', 'logo-lossless.webp', 100, true);
Imagick's webp:method setting trades encoding speed for compression ratio. Method 6 is slowest but ~5% smaller than method 4 (default). For batch processing where speed matters, use method 4 or lower.
Method 3: ChangeThisFile API (curl or Guzzle)
The API handles the conversion server-side. Source format is auto-detected from the filename — just pass target=webp. Free tier: 1,000 conversions/month, no card.
true,
CURLOPT_TIMEOUT => 60,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => [
'file' => new CURLFile($srcPath, 'image/jpeg', basename($srcPath)),
'target' => 'webp',
],
]);
$result = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code !== 200) throw new RuntimeException("API error: HTTP {$code}");
file_put_contents($outPath, $result);
}
jpgToWebpApi('photo.jpg', 'photo.webp', 'ctf_sk_your_key_here');
echo "Done\n";
When to use each
| Approach | Best for | Tradeoff |
|---|---|---|
| GD | Simple bulk conversion, no extra installs | Less control over encoding; quality scale differs from JPEG |
| Imagick | Metadata control, lossless mode, fine-tuned encoding | Needs php-imagick extension |
| ChangeThisFile API | Zero server config, shared hosting | Network call per image; free tier 25MB limit |
Production tips
- WebP quality 80 ≈ JPEG quality 90. Don't map quality 1:1 between formats — WebP's encoder is more efficient at the same numeric setting.
- Serve WebP with JPEG fallback. Use the Accept header or <picture> with <source type="image/webp"> so older clients get JPEG. PHP: check str_contains($_SERVER['HTTP_ACCEPT'], 'image/webp').
- Strip EXIF on conversion. JPEG files often embed GPS coordinates and device info. WebP doesn't need this — strip it with Imagick's stripImage() or add it as a pipeline step.
- Preserve originals. Store both JPG and WebP. If you overwrite the JPEG, you lose the lossless original — re-encoding from WebP adds another generation of lossy compression.
- Cache busting for WebP swaps. If you replace an existing .jpg URL with a WebP version, CDNs may serve cached JPEGs. Use versioned filenames or add a cache-control max-age=0 header when updating.
GD is the fastest path for bulk JPG-to-WebP conversion in PHP — it's already installed on most servers. Imagick is worth the setup if you need lossless mode or EXIF stripping. Free API key: 1,000 conversions/month.