
8 Ways to Convert Images to WebP in Bulk
Convert PNG and JPG to WebP with cwebp, sharp, ImageMagick, libvips and CDNs. Which quality setting matches your JPEG, when lossless wins, and how to serve WebP without breaking anything.
WebP is 25–35% smaller than JPEG at matched quality and typically 30–50% smaller than PNG for graphics. Every current browser decodes it. Converting your site’s images is usually the largest single performance win available for the least work.
The conversion is easy. The two decisions that matter are lossy or lossless, and what quality number matches what you have now. Get those wrong and you either ship blurry screenshots or files bigger than the originals.
Pick Your Method First
| # | Method | Speed | Batch? | Best for |
|---|---|---|---|---|
| 1 | Browser converter | Fast | Yes | A handful of files |
| 2 | cwebp |
Fast | Yes | The reference encoder |
| 3 | ImageMagick | Moderate | Yes | Already in your pipeline |
| 4 | libvips | Fastest | Yes | Thousands of files |
| 5 | sharp (Node) | Fastest | Yes | Node build steps |
| 6 | Python and Pillow | Moderate | Yes | Python pipelines |
| 7 | Build tools | Automatic | Yes | Sites with a build step |
| 8 | An image CDN | Automatic | Yes | No pipeline at all |
Lossy or Lossless?
WebP is two encoders sharing one file extension, and choosing the wrong one is the most common mistake.
Lossy WebP uses prediction and transforms similar to a video keyframe. It is the right choice for photographs, and it is what you compare against JPEG.
Lossless WebP is a completely different algorithm. It is the right choice for screenshots, logos, icons, diagrams and anything with text or flat colour, and it is what you compare against PNG. It typically beats PNG by 20–30%.
| Source | Use | Why |
|---|---|---|
| Photograph | Lossy, -q 75 to -q 82 |
Matches JPEG q85 at a smaller size |
| Screenshot, UI capture | Lossless | Text edges stay crisp |
| Logo, icon, line art | Lossless | Hard edges must not blur |
| Diagram or chart | Lossless or -near_lossless |
Flat colour compresses perfectly |
| Photo with transparency | Lossy, with -alpha_q 100 |
Lossy WebP supports alpha; JPEG does not |
Running a screenshot through lossy WebP produces the same grey halos around letters that JPEG does. It is the single most visible way to get this wrong.
What Quality Number Matches Your JPEG?
WebP’s quality scale is not the same as JPEG’s. The same number produces a different result.
| Your JPEG | Equivalent WebP | Typical saving |
|---|---|---|
| q95 | -q 88 |
30% |
| q90 | -q 82 |
30% |
| q85 | -q 75 |
25–30% |
| q80 | -q 70 |
25% |
As a rule of thumb, subtract about 8 to 10 from your JPEG quality to get an equivalent WebP quality. -q 75 is a good default for photographic content, and it is what most CDNs use.
Do not simply pass your existing number through. Converting a q85 JPEG at -q 85 gives you a file that is barely smaller, and people conclude WebP does not help.
The two curves show why. Both encoders were run across the same quality range on the same 1200×630 source:
WebP vs JPEG File Size Across the Quality Range
Same 1200×630 source encoded with cwebp and ImageMagick at each quality setting.
Two things stand out. WebP is smaller at every setting, and the gap widens sharply above q85 — at q100 the JPEG is more than three times the size of the WebP. Matching JPEG q85 (125 KB) with WebP q75 (66 KB) saved 47% here.
1. A Browser Converter
For a handful of files, or on a machine where you cannot install anything.
- PNG to WebP
- JPG to WebP
- PNG to AVIF — smaller still, if you want the next step
The encode runs locally through WebAssembly, so nothing is uploaded.
2. cwebp
Google’s reference encoder, from the same libwebp package as dwebp. It exposes every setting and produces the definitive output.
# Install
sudo apt install webp # Debian/Ubuntu
brew install webp # macOS
winget install Google.libwebp
# Photograph
cwebp -q 78 photo.jpg -o photo.webp
# Maximum effort, slower encode, 2-5% smaller
cwebp -q 78 -m 6 photo.jpg -o photo.webp
# Screenshot or logo: lossless
cwebp -lossless -z 9 screenshot.png -o screenshot.webp
# Near-lossless: visually identical, much smaller than lossless
cwebp -near_lossless 60 -z 9 diagram.png -o diagram.webp
# Transparency at full quality with a lossy body
cwebp -q 80 -alpha_q 100 logo.png -o logo.webp
# Use all cores
cwebp -q 78 -mt photo.jpg -o photo.webp
# A whole folder
for f in *.jpg; do cwebp -q 78 -m 6 "$f" -o "${f%.jpg}.webp"; done
# In parallel
ls *.jpg | xargs -P 8 -I {} sh -c 'cwebp -q 78 "{}" -o "${1%.jpg}.webp"' _ {}
-near_lossless deserves more attention than it gets. It applies a small amount of pre-processing before lossless encoding, adjusting pixel values so they compress better without a visible change.
Measured on a UI screenshot, all at maximum effort: pure lossless was 209 KB, -near_lossless 80 was 193 KB, -near_lossless 60 was 184 KB, and -near_lossless 20 was 161 KB. The saving is real but modest — around 12% at the usual setting, not the halving sometimes claimed.
One catch worth knowing: -near_lossless without -z 9 performed worse than plain lossless in testing. Always pair the two.
-m 6 sets the maximum compression effort. Measured on the same 1200×630 source, cwebp -q 78 took 59 ms and cwebp -q 78 -m 6 took 105 ms — roughly double the encode time for a few percent smaller output. Worth it for assets you encode once and serve millions of times, not worth it for a one-off.
WebP Quality vs File Size
cwebp across its quality range on a 1200×630 source. The curve is flat until about q85, then climbs sharply.
The shape is the reason -q 78 is a good default. Going from 78 to 100 more than doubles the file for a difference you will not see at normal viewing size, while dropping from 78 to 60 saves only about 20 KB and starts to show on gradients.
Animated GIF to Animated WebP
gif2webp -q 70 -m 6 animation.gif -o animation.webp
Animated WebP supports full colour instead of 256 and works in a plain <img> tag. The size saving is smaller than usually claimed, though, and it depends on the source: converting a gradient-heavy GIF this way measured about 14% smaller, while a GIF of a flat interface came out larger, because WebP then has to encode the dithering noise the GIF introduced.
The saving comes from encoding the original video, not the finished GIF. And if the destination allows a video at all, an MP4 beats both by an order of magnitude — see our video to GIF guide for the measurements and our guide to modern animated formats for the full comparison.
3. ImageMagick
Convenient when you are already resizing or otherwise processing.
# Check the delegate exists
magick -list format | grep -i webp
# One file
magick photo.jpg -quality 78 photo.webp
# A folder
magick mogrify -format webp -quality 78 *.jpg
# Lossless, for graphics
magick mogrify -format webp -define webp:lossless=true *.png
# Resize and convert in one pass
magick mogrify -format webp -quality 78 -resize 1600x1600\> *.jpg
# Expose the cwebp knobs
magick photo.jpg -quality 78 -define webp:method=6 -define webp:alpha-quality=100 photo.webp
ImageMagick calls libwebp underneath, so the output is comparable to cwebp. It is slower, because ImageMagick decodes into its own in-memory representation first.
4. libvips
The fastest option by a wide margin, and the one to use when the folder has thousands of files. libvips streams images through a pipeline instead of loading each one whole, so both time and peak memory are dramatically lower than ImageMagick’s.
# Install
sudo apt install libvips-tools
brew install vips
# One file
vips copy photo.jpg photo.webp[Q=78]
# Resize and convert, the common web case
vipsthumbnail photo.jpg -s 1600 -o photo.webp[Q=78]
# A folder, in parallel
vipsthumbnail *.jpg -s 1600 -o %s-1600.webp[Q=78]
# Lossless for graphics
vips copy screenshot.png screenshot.webp[lossless=true]
On a folder of several thousand photographs the difference against ImageMagick is usually several-fold in wall-clock time, with far lower memory use. For a one-off conversion of twenty files it does not matter; for a nightly job over a media library it decides whether the job finishes.
5. sharp, in Node
sharp wraps libvips, so it inherits the speed. This is the standard choice in a JavaScript build step.
// npm install sharp
import sharp from "sharp";
import { readdir } from "fs/promises";
const files = (await readdir("src/images")).filter(f => /\.(jpe?g|png)$/i.test(f));
await Promise.all(files.map(async (file) => {
const input = `src/images/${file}`;
const output = `dist/images/${file.replace(/\.\w+$/, ".webp")}`;
const isGraphic = /\.png$/i.test(file);
await sharp(input)
.resize({ width: 1600, withoutEnlargement: true })
.webp(isGraphic
? { lossless: true }
: { quality: 78, effort: 6 })
.toFile(output);
}));
withoutEnlargement: true is important. Without it, a 400-pixel-wide image gets upscaled to 1600, producing a blurry file larger than the original.
Generating several widths at once, for a srcset:
const widths = [400, 800, 1200, 1600];
await Promise.all(widths.map(width =>
sharp(input)
.resize({ width, withoutEnlargement: true })
.webp({ quality: 78 })
.toFile(`dist/photo-${width}.webp`)
));
6. Python and Pillow
# pip install pillow
from PIL import Image
from pathlib import Path
for path in Path("images").glob("*.[jp][pn]g"):
image = Image.open(path)
is_graphic = path.suffix.lower() == ".png"
image.save(
path.with_suffix(".webp"),
"WEBP",
lossless=is_graphic,
quality=78 if not is_graphic else 100,
method=6,
)
Pillow’s WebP support is built on libwebp, so quality matches cwebp at the same settings. method=6 is the same maximum-effort switch as cwebp -m 6.
7. Build Tools
The best place for this work is your build, so nobody has to remember to do it.
Astro has <Picture> built in, which emits WebP and AVIF alongside a fallback:
---
import { Picture } from 'astro:assets';
import hero from '../assets/hero.jpg';
---
<Picture src={hero} formats={['avif', 'webp']} alt="" widths={[400, 800, 1200]} />
Vite — vite-imagetools converts on import with a query string:
import hero from './hero.jpg?format=webp&quality=78&w=1600'
Next.js — the built-in next/image component converts to WebP or AVIF on demand and caches the result, with no build configuration. See our Next.js image guide.
WordPress — recent versions generate WebP for uploads automatically. Plugins such as ShortPixel and EWWW handle existing media libraries. Our WordPress optimizer plugin guide compares them.
Our build tool image plugins guide covers the full range.
8. An Image CDN
The option with no pipeline at all: keep your originals as they are, and let the delivery layer convert on request.
A CDN inspects the browser’s Accept header and returns WebP to browsers that support it, AVIF to those that support that, and JPEG to anything else — from the same URL. You never run a conversion, never store multiple copies, and never write <picture> markup.
Sirv does this automatically, along with resizing and cropping from URL parameters, so one master file serves every size and format your pages need. You can try it free against your existing images.
This is also the only approach that adapts as formats change. When AVIF or JPEG XL support shifts, the CDN’s behaviour updates without you re-encoding anything. Our image CDN comparison covers the options, and signs you need an image CDN covers whether it is worth it for you.
Serving WebP Safely
Converting is half the job. Serving it correctly is the other half.
With <picture>, when you host the files yourself:
<picture>
<source srcset="photo.avif" type="image/avif">
<source srcset="photo.webp" type="image/webp">
<img src="photo.jpg" alt="Description" width="1600" height="900">
</picture>
The browser takes the first <source> it can decode. The <img> is the fallback and is required — it also carries the alt, width and height.
Always set width and height. Without them the browser cannot reserve space, and the page shifts as images load. That is a Cumulative Layout Shift penalty, and it is the most common self-inflicted Core Web Vitals problem. See image optimization for Core Web Vitals.
Check the server sends the right MIME type. WebP must be served as image/webp. Most servers know this, but a minimal or misconfigured one may send application/octet-stream, which makes browsers download the file rather than display it.
Keep Your Originals
Never delete the source files after converting.
WebP is lossy in its usual mode, so re-encoding from a WebP to something else compounds the loss. You will also want to re-encode at different settings later, generate additional sizes, or move to AVIF or JPEG XL when they suit you better. All of that requires the master.
Keep the originals in a lossless format outside your web root, and treat every WebP as a disposable derivative.
Summary
The Eight Methods
| # | Method | Use it when |
|---|---|---|
| 1 | Browser converter | A handful of files, nothing installed |
| 2 | cwebp |
The reference, with every setting exposed |
| 3 | ImageMagick | It is already in your pipeline |
| 4 | libvips | Thousands of files, or limited memory |
| 5 | sharp | A Node build step |
| 6 | Pillow | A Python pipeline |
| 7 | Build tools | A site that already has a build |
| 8 | An image CDN | No pipeline, and formats that keep up |
Checklist
- ✅ Photographs used lossy, graphics used lossless
- ✅ Quality is about 8–10 points below your old JPEG number
- ✅
-near_losslesswas tried on screenshots and diagrams - ✅
-m 6was used for assets encoded once and served often - ✅ Images are not upscaled during the resize step
- ✅
<picture>has an<img>fallback withwidthandheight - ✅ The server sends
image/webp - ✅ The lossless originals are kept somewhere safe
cwebp -q 78 -m 6 photo.jpg -o photo.webp for photographs, and cwebp -near_lossless 60 -z 9 shot.png -o shot.webp for screenshots. Those two commands cover most of a website.
Related Resources
Related Guides
8 Ways to Convert WebP to PNG or JPG (and Why Sites Serve WebP)
The Complete WebP Guide: Everything You Need to Know
WebP vs AVIF vs JPEG XL: Which Format When?
PNG to JPG and Back: 9 Ways to Convert Without Wrecking the Image
Batch Image Processing Workflows: Scale Your Optimization
Image Optimization for Core Web Vitals