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

ApproachBest forTradeoff
GDSimple bulk conversion, no extra installsLess control over encoding; quality scale differs from JPEG
ImagickMetadata control, lossless mode, fine-tuned encodingNeeds php-imagick extension
ChangeThisFile APIZero server config, shared hostingNetwork 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.