WebP is a modern image format developed by Google that delivers significantly smaller files than PNG and JPG while maintaining the same visual quality. Switching to WebP can cut your page weight by 25–50% — one of the highest-impact performance wins available to web developers.
WebP vs PNG vs JPG vs AVIF
| Format | Compression | Transparency | Animation | Browser support | Best for |
|---|---|---|---|---|---|
| JPG/JPEG | Lossy | No | No | Universal | Photographs |
| PNG | Lossless | Yes | No | Universal | Logos, screenshots, graphics |
| GIF | Lossless (8-bit) | Yes (1-bit) | Yes | Universal | Simple animations |
| WebP | Lossy + lossless | Yes | Yes | 96% (all modern) | Web images (replaces JPG + PNG + GIF) |
| AVIF | Lossy + lossless | Yes | Yes | 92% (modern) | Next-gen web (smaller than WebP) |
WebP covers everything JPG, PNG, and GIF do — in a single format. The only reason to keep JPG or PNG is compatibility with older browsers (IE11, very old Safari) or software that doesn't support WebP yet.
How much smaller is WebP?
Google's own benchmarks show WebP is:
- 25–34% smaller than JPG at equivalent visual quality
- 26% smaller than PNG for lossless compression
Real-world results depend on the image, but here are typical comparisons:
| Image | JPG size | PNG size | WebP (lossy) | WebP (lossless) |
|---|---|---|---|---|
| Photo (1920×1080) | 420 KB | 3.2 MB | 310 KB | 1.9 MB |
| Logo with transparency | — | 85 KB | — | 62 KB |
| Screenshot (1440×900) | 290 KB | 1.8 MB | 195 KB | 1.1 MB |
| Animation (30 frames) | — | — (GIF: 2.1 MB) | 890 KB | — |
JavaScript (browser + Node.js)
Browser: Canvas API
function convertToWebP(file, quality = 0.85) {
return new Promise((resolve, reject) => {
const img = new Image();
const url = URL.createObjectURL(file);
img.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
// 'image/webp' with quality 0–1
canvas.toBlob(
(blob) => {
URL.revokeObjectURL(url);
resolve(blob);
},
'image/webp',
quality
);
};
img.onerror = () => reject(new Error('Failed to load image'));
img.src = url;
});
}
// Usage
const input = document.querySelector('input[type="file"]');
input.addEventListener('change', async (e) => {
const file = e.target.files[0];
const webpBlob = await convertToWebP(file, 0.85);
const a = document.createElement('a');
a.href = URL.createObjectURL(webpBlob);
a.download = file.name.replace(/\.[^.]+$/, '.webp');
a.click();
});
Note: canvas.toBlob('image/webp') is supported in all modern browsers except Safari on iOS 13 and earlier. Safari added WebP support in version 14 (2020).
Node.js: Sharp
import sharp from 'sharp';
import { readdir } from 'fs/promises';
import path from 'path';
// Single file
async function toWebP(inputPath, quality = 80) {
const outputPath = inputPath.replace(/\.[^.]+$/, '.webp');
await sharp(inputPath)
.webp({ quality })
.toFile(outputPath);
return outputPath;
}
// Batch convert a folder
async function batchToWebP(dir, quality = 80) {
const files = await readdir(dir);
const images = files.filter(f => /\.(png|jpg|jpeg|gif|tiff|bmp)$/i.test(f));
for (const file of images) {
const inputPath = path.join(dir, file);
const outputPath = path.join(dir, file.replace(/\.[^.]+$/, '.webp'));
await sharp(inputPath).webp({ quality }).toFile(outputPath);
console.log(`✓ ${file} → ${path.basename(outputPath)}`);
}
}
// Lossless WebP (for PNG-like quality)
async function toLosslessWebP(inputPath) {
const outputPath = inputPath.replace(/\.[^.]+$/, '.webp');
await sharp(inputPath)
.webp({ lossless: true })
.toFile(outputPath);
return outputPath;
}
Python
from PIL import Image
from pathlib import Path
import glob
def to_webp(input_path: str, quality: int = 80, lossless: bool = False) -> str:
"""Convert any Pillow-supported format to WebP."""
img = Image.open(input_path)
# Preserve EXIF orientation
try:
from PIL import ImageOps
img = ImageOps.exif_transpose(img)
except Exception:
pass
output_path = Path(input_path).with_suffix('.webp')
save_kwargs = {'format': 'WEBP', 'quality': quality}
if lossless:
save_kwargs['lossless'] = True
# WebP supports RGBA — no need to flatten transparency
img.save(str(output_path), **save_kwargs)
return str(output_path)
def batch_to_webp(folder: str, quality: int = 80) -> None:
"""Batch convert all images in a folder to WebP."""
patterns = ['*.jpg', '*.jpeg', '*.png', '*.gif', '*.bmp', '*.tiff']
for pattern in patterns:
for path in glob.glob(f'{folder}/{pattern}', recursive=False):
try:
out = to_webp(path, quality)
print(f'✓ {path} → {out}')
except Exception as e:
print(f'✗ {path}: {e}')
# Usage
to_webp('photo.jpg', quality=85) # lossy, 85% quality
to_webp('logo.png', lossless=True) # lossless (same as PNG quality)
batch_to_webp('./images', quality=80) # convert whole folder
Pillow WebP notes:
- WebP supports RGBA natively — transparent PNGs convert correctly without flattening
- GIF animations: Pillow saves only the first frame; use
imageioorffmpegfor animated WebP - Install:
pip install Pillow
Go
package main
import (
"fmt"
"image"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"os"
"path/filepath"
"strings"
"github.com/chai2010/webp"
)
// ToWebP converts any standard image to WebP.
// quality: 1–100 (80 is a good default); use 0 for lossless.
func ToWebP(inputPath string, quality float32, lossless bool) (string, error) {
f, err := os.Open(inputPath)
if err != nil {
return "", fmt.Errorf("open: %w", err)
}
defer f.Close()
img, _, err := image.Decode(f)
if err != nil {
return "", fmt.Errorf("decode: %w", err)
}
ext := filepath.Ext(inputPath)
outputPath := strings.TrimSuffix(inputPath, ext) + ".webp"
out, err := os.Create(outputPath)
if err != nil {
return "", fmt.Errorf("create output: %w", err)
}
defer out.Close()
var encErr error
if lossless {
encErr = webp.EncodeLosslessRGBA(out, img)
} else {
encErr = webp.EncodeRGBA(out, img, quality)
}
if encErr != nil {
return "", fmt.Errorf("encode webp: %w", encErr)
}
return outputPath, nil
}
func main() {
out, err := ToWebP("photo.jpg", 80, false)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Converted:", out)
}
Install the library: go get github.com/chai2010/webp
The standard library (image/jpeg, image/png, image/gif) handles decoding. The chai2010/webp package handles WebP encoding. Go has no built-in WebP encoder.
PHP
<?php
/**
* Convert any GD-supported image to WebP.
* Returns the output path on success, throws on failure.
*/
function toWebP(string $inputPath, int $quality = 80, bool $lossless = false): string {
$info = getimagesize($inputPath);
if ($info === false) {
throw new \RuntimeException("Cannot read image: $inputPath");
}
$src = match ($info[2]) {
IMAGETYPE_JPEG => imagecreatefromjpeg($inputPath),
IMAGETYPE_PNG => imagecreatefrompng($inputPath),
IMAGETYPE_GIF => imagecreatefromgif($inputPath),
IMAGETYPE_BMP => imagecreatefrombmp($inputPath),
default => throw new \RuntimeException("Unsupported format: {$info['mime']}"),
};
// Preserve PNG/GIF transparency
if (in_array($info[2], [IMAGETYPE_PNG, IMAGETYPE_GIF])) {
imagepalettetotruecolor($src);
imagealphablending($src, false);
imagesavealpha($src, true);
}
$outputPath = preg_replace('/\.[^.]+$/', '.webp', $inputPath);
if ($lossless) {
// quality = -1 for lossless in PHP's imagewebp
imagewebp($src, $outputPath, -1);
} else {
imagewebp($src, $outputPath, $quality);
}
imagedestroy($src);
return $outputPath;
}
// Batch convert a directory
function batchToWebP(string $dir, int $quality = 80): void {
$extensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp'];
foreach (new DirectoryIterator($dir) as $file) {
if ($file->isDot() || !$file->isFile()) continue;
if (!in_array(strtolower($file->getExtension()), $extensions)) continue;
try {
$out = toWebP($file->getPathname(), $quality);
echo "✓ {$file->getFilename()} → " . basename($out) . PHP_EOL;
} catch (\Exception $e) {
echo "✗ {$file->getFilename()}: {$e->getMessage()}" . PHP_EOL;
}
}
}
// Usage
toWebP('photo.jpg', 85); // lossy
toWebP('logo.png', 80, true); // lossless
batchToWebP('./images', 80); // batch
?>
Requirements: PHP must be compiled with --with-webp (enabled by default since PHP 8.1 on most hosts). Check with phpinfo() — look for WebP Support under the GD section.
Quick reference
| Task | JavaScript | Python | Go | PHP |
|---|---|---|---|---|
| Lossy WebP | canvas.toBlob('image/webp', q) |
img.save(path, 'WEBP', quality=q) |
webp.EncodeRGBA(out, img, q) |
imagewebp($src, $path, q) |
| Lossless WebP | canvas.toBlob('image/webp', 1.0) |
img.save(path, 'WEBP', lossless=True) |
webp.EncodeLosslessRGBA(out, img) |
imagewebp($src, $path, -1) |
| Library | built-in / Sharp | Pillow | chai2010/webp | GD (built-in) |
| Transparency | Preserved | Preserved | Preserved | Preserved |
| Animated WebP | Not via Canvas | Use imageio | Not in stdlib | Not in GD |
Using an online converter
For quick one-off conversions without writing code, the image converter tool converts PNG, JPG, GIF, BMP, and TIFF to WebP (and other formats) directly in your browser — no upload to a server, no file size limit for local processing.
6 common mistakes
1. Using WebP for every use case without a fallback
WebP has 96% browser support, but if you need IE11 or very old Safari support, provide a JPG/PNG fallback using the <picture> element:
<picture>
<source srcset="photo.webp" type="image/webp">
<img src="photo.jpg" alt="Photo">
</picture>
2. Expecting lossless WebP to always be smaller than PNG Lossless WebP is smaller than PNG for photographs and complex images, but sometimes PNG is smaller for simple graphics with large flat-colour areas. Always compare sizes.
3. Converting a JPG to WebP at quality 100 If your source is already a lossy JPG, converting it at quality 100 (or lossless) won't recover lost detail — it will just create a very large WebP file. Use quality 80–90 for JPG sources.
4. Skipping EXIF orientation handling
Some JPEGs have rotation stored as EXIF metadata rather than in the pixel data. When you decode and re-encode such a file, the rotation may be lost. Fix this by applying EXIF transpose before encoding (Pillow's ImageOps.exif_transpose, Sharp's automatic .rotate() call with no arguments).
5. Assuming animated GIF → animated WebP is automatic
Most simple converters only grab the first frame. True animated WebP requires a dedicated step (ffmpeg, imageio, or libwebp's gif2webp CLI tool).
6. Using WebP for open-graph (OG) images on social media
Facebook, Twitter/X, LinkedIn, and WhatsApp thumbnail scrapers may not support WebP for OG images. Keep og:image as JPG or PNG.
FAQ
Is WebP better than AVIF? AVIF is slightly smaller than WebP (roughly 10–20% for photographs) but has lower browser support (92% vs 96%) and slower encoding. Use AVIF for the highest compression where encoding time allows; use WebP as the practical modern default.
Can I convert WebP back to PNG or JPG? Yes. All the libraries above can also decode WebP using the same approach in reverse (decode WebP → encode JPG/PNG). Sharp, Pillow, and PHP GD all support WebP as an input format.
Does WebP support transparency like PNG? Yes — WebP supports a full 8-bit alpha channel, so logos and icons with transparent backgrounds convert without any flattening needed.
Will converting to WebP affect image quality? Lossy WebP at quality 80–90 is visually indistinguishable from JPG at equivalent quality for photographs. For pixel-perfect fidelity (logos, screenshots, text), use lossless WebP.
What about serving WebP in email? Gmail, Outlook, and Apple Mail have inconsistent WebP support. Stick to JPG or PNG for email images.
Can CSS background images use WebP?
Yes. All modern browsers that support WebP support it as a CSS background-image. For older browser fallbacks, use @supports or serve a JPG fallback from the server.