How to Convert PNG to WebP in Node.js
Convert PNG images to WebP in Node.js with Sharp, tune quality and metadata, handle buffers, and troubleshoot common production errors.
Use Sharp: install it with npm install sharp, then call sharp(input.png).webp().toFile('output.webp'). Sharp supports PNG input and WebP output, with configurable quality, lossless modes, effort, alpha handling and metadata behavior. See the Sharp project overview and output API documentation.
Convert a PNG file to WebP
1. Create a project
mkdir png-to-webp
cd png-to-webp
npm init -y
npm install sharp
Place an input file named input.png in the project directory. The current Sharp overview documents Node.js 20.9.0 or newer and notes that most modern macOS, Windows and Linux systems do not need extra runtime dependencies. Check the installed release documentation when choosing a deployment runtime because these requirements can change.
2. Use an ES module
// convert.mjs
import sharp from 'sharp';
try {
const info = await sharp('input.png')
.webp()
.toFile('output.webp');
console.log(`Wrote ${info.size} bytes at ${info.width}x${info.height}`);
} catch (error) {
console.error('PNG to WebP conversion failed:', error);
process.exitCode = 1;
}
node convert.mjs
toFile() returns a Promise when no callback is supplied. Its result includes information such as format, byte size, dimensions and channel count.
3. CommonJS version
// convert.cjs
const sharp = require('sharp');
async function main() {
const info = await sharp('input.png')
.webp()
.toFile('output.webp');
console.log(info);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Convert a Buffer instead of writing an input file
Use toBuffer() when the PNG comes from an upload, object storage or another service and the WebP must be returned or uploaded without an intermediate output file.
import sharp from 'sharp';
import { readFile } from 'node:fs/promises';
const png = await readFile('input.png');
const webp = await sharp(png)
.webp({ quality: 80 })
.toBuffer();
await import('node:fs/promises').then(({ writeFile }) =>
writeFile('output.webp', webp)
);
console.log(`Created ${webp.length} bytes`);
In an HTTP server, send webp with Content-Type: image/webp instead of writing it to disk.
Control WebP quality and encoding
The documented default quality is 80 and the default effort is 4. These are starting points, not universal best settings. Compare representative images for appearance, output size and processing cost.
import sharp from 'sharp';
await sharp('input.png')
.webp({
quality: 82,
alphaQuality: 90,
effort: 5,
smartSubsample: true
})
.toFile('output.webp');
| Option | What it controls | When to consider it |
|---|---|---|
quality |
Lossy image quality, from 1 to 100 | Reduce bytes while preserving acceptable visual detail |
alphaQuality |
Quality of transparency data | Images with transparent edges or overlays |
lossless |
Lossless WebP encoding | Exact pixel preservation is required |
nearLossless |
Near-lossless encoding | Reduce size while keeping the image close to the source |
smartSubsample |
Chroma-subsampling behavior | Adjust color-detail tradeoffs in lossy output |
preset |
Encoder preset | Use a documented preset suited to the image type |
effort |
Encoding effort from 0 to 6 | Trade processing time for potentially smaller output |
For lossless output:
await sharp('input.png')
.webp({ lossless: true })
.toFile('output-lossless.webp');
Do not assume WebP always produces a smaller file or that one quality value is optimal. Measure your own representative photographs, screenshots, illustrations and transparent assets.
Metadata and image orientation
Sharp removes metadata by default, including EXIF-based orientation. This is useful when you want a normalized, smaller output, but it can remove information your workflow needs. Preserve metadata explicitly with withMetadata().
import sharp from 'sharp';
await sharp('input.png')
.withMetadata()
.webp({ quality: 80 })
.toFile('output-with-metadata.webp');
Decide whether metadata retention is necessary before publishing converted files. If the source orientation is important, inspect the rendered result after conversion rather than relying only on the filename.
Resize while converting
Sharp operations can be chained. Resize before encoding when you need a smaller delivery image.
import sharp from 'sharp';
await sharp('input.png')
.resize({ width: 1600, withoutEnlargement: true })
.webp({ quality: 80 })
.toFile('output-1600.webp');
Keep the resize policy separate from the format policy: changing dimensions affects detail and byte size independently from WebP quality.
Batch conversion
For a small, controlled set of files, process them with a loop and preserve failures per file.
import sharp from 'sharp';
import { readdir } from 'node:fs/promises';
import path from 'node:path';
const files = (await readdir('png')).filter((name) => name.toLowerCase().endsWith('.png'));
for (const name of files) {
const source = path.join('png', name);
const destination = path.join('webp', name.replace(/\.png$/i, '.webp'));
try {
const info = await sharp(source).webp({ quality: 80 }).toFile(destination);
console.log(`${name}: ${info.size} bytes`);
} catch (error) {
console.error(`${name}: failed`, error);
}
}
Make sure the webp directory already exists and that the process can read the source files and write the destination files.
Performance, reliability and cost considerations
- Memory: buffer-based workflows keep encoded data in memory. Bound upload sizes and avoid loading an unbounded number of images at once.
- Throughput: higher
effortcan increase processing work. Choose it by measuring your own workload. - Concurrency: use a bounded queue for large batches so concurrent conversions do not exhaust CPU or memory.
- Atomic writes: write to a temporary destination and rename it after a successful conversion when readers may access the output concurrently.
- Retries: retry only transient file or storage failures. A corrupt PNG, missing path or permission error will not be fixed by retrying.
- Cost: Sharp is an npm dependency; conversion cost comes from the CPU, memory and storage used by your runtime. The research does not establish a universal benchmark or percentage size reduction.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Cannot find module 'sharp' |
The package is not installed in the current project | Run npm install sharp in the project directory and deploy the updated lockfile and dependencies. |
| Input file not found | Relative path is resolved from the process working directory | Log the resolved path, use an absolute path when appropriate, and verify the file exists before calling Sharp. |
| Permission denied | The process cannot read the PNG or write the destination | Check filesystem ownership and permissions, and choose a writable output directory. |
| Output is unexpectedly larger | Source content, dimensions or selected quality favor a larger WebP | Compare representative files, adjust quality or resize, and keep the format that meets your requirements. |
| Transparency looks wrong | Alpha quality or the source’s transparent pixels need inspection | Test alphaQuality, compare lossless output, and inspect the result on the intended background. |
| Orientation changed | Metadata, including EXIF orientation, is stripped by default | Use withMetadata() when metadata retention is required, then verify the rendered image. |
| Deployment fails after local success | Runtime or platform differs from local development | Check the installed Sharp documentation and Node.js version for the deployment environment. |
Or skip the browser setup
If your actual task is capturing a web page as an image before further processing, ScreenshotNeo returns PNG, JPEG or WebP from one GET request. Its API accepts the target URL and can produce a clean capture after accepting cookie or consent banners and removing more than 60 known consent platforms, newsletter popups and chat widgets.
See the ScreenshotNeo API documentation for the request options. The basic calls are:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Cookie banners, popups and chat widgets are removed before the shot. Bot checks, blank pages and failed loads are never billed, and response headers identify the page verdict and whether the shot was billed. An MCP server lets AI agents use take_screenshot, get_page_info and capture_pdf. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots.
Create a free ScreenshotNeo account.
FAQ
Can Sharp convert PNG to WebP without saving a file first?
Yes. Call toBuffer() after .webp() and return or upload the resulting buffer.
What quality should I use?
Start with the documented default of 80, then compare representative images. There is no quality value that is best for every image set.
Does conversion preserve EXIF data?
No. Sharp strips metadata by default. Use withMetadata() when the output must retain it.
When should I use lossless WebP?
Use the documented lossless mode when exact pixel preservation matters, and verify output size and appearance for your assets.


