Format Guide14 min read

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.

By ImageGuide Team·Published August 15, 2026·Updated August 15, 2026
webpcwebpsharpconversionbatchperformance

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.

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]} />

Vitevite-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

  1. ✅ Photographs used lossy, graphics used lossless
  2. ✅ Quality is about 8–10 points below your old JPEG number
  3. -near_lossless was tried on screenshots and diagrams
  4. -m 6 was used for assets encoded once and served often
  5. ✅ Images are not upscaled during the resize step
  6. <picture> has an <img> fallback with width and height
  7. ✅ The server sends image/webp
  8. ✅ 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

Format References

Ready to optimize your images?

Sirv automatically optimizes, resizes, and converts your images. Try it free.

Start Free Trial