sharp
sharp is the standard image processing library for Node.js. It wraps libvips, a C library, behind a chainable JavaScript API: load an image from a file, Buffer, or stream, then resize, crop, rotate, composite, and re-encode it as JPEG, PNG, WebP, GIF, or AVIF. The README claims resizing is typically 4x-5x faster than the fastest ImageMagick settings, and in practice it is the piece that powers most Node image pipelines, including Next.js image optimization. It works on Node 20.9+, Deno, and Bun via Node-API v9.
If you process images in Node and can tolerate native binaries, install sharp and stop looking; nothing else in the ecosystem is close on speed or correctness. The only real costs are deployment friction on mismatched platforms and a 0.x version scheme that makes minor bumps worth reading the changelog for.
Use it if
- You generate thumbnails, responsive image sets, or WebP/AVIF variants server-side and care about throughput
- You need correct handling of EXIF orientation, ICC color profiles, and alpha channels without doing it by hand
- You process user uploads and want stream or Buffer pipelines instead of writing temp files
- You are on Node 20.9+, Deno, or Bun and can ship platform-specific native binaries
- You cannot ship native binaries: sharp installs a prebuilt libvips per platform, and locked-down build environments, unusual architectures, or strict allow-listed registries turn install into a project of its own
- You need image processing in the browser: sharp is server-side only, there is no browser build
- Your deploy target differs from your dev machine (Alpine, Lambda, cross-compiled Docker): you will spend time on npm install flags like --os/--cpu and multi-stage builds before anything works
- You only crop an avatar once a week: jimp is pure JavaScript, installs anywhere, and is fast enough for light or occasional work
- You track semver strictly: sharp is still 0.x after a decade, so breaking changes arrive in minor versions (0.33 to 0.34 changed rotation behavior, for example)
Setup reality
npm install sharp usually just works on mainstream macOS, Windows, and Linux because prebuilt binaries exist for those platforms. The pain starts at the edges: cross-platform installs (developing on macOS, deploying to Linux Lambda or Alpine) need explicit --os, --cpu, and sometimes --libc install flags or optionalDependencies overrides; pnpm needs sharp allow-listed for postinstall scripts in some setups; and corporate proxies that block GitHub release downloads used to be a classic failure mode. Requires Node 20.9+, which rules out older LTS images. Budget an hour the first time you containerize it.
Patterns
Resize to a target width, keeping aspect ratioresize-image
import sharp from 'sharp';
await sharp('input.jpg')
.resize({ width: 800 })
.toFile('output.jpg');Omit height and sharp preserves aspect ratio; by default small images ARE enlarged, pass withoutEnlargement: true to prevent upscaling.
Convert any input to WebP with a quality settingconvert-to-webp
const webp = await sharp('input.png')
.webp({ quality: 80 })
.toBuffer();For animated GIF/WebP input you must open with sharp(input, { animated: true }) or you only get the first frame.
Center-cropped square thumbnailsquare-thumbnail
await sharp('input.jpg')
.resize(300, 300, { fit: 'cover', position: 'attention' })
.toFile('thumb.jpg');position: 'attention' crops toward the region with highest detail; drop it to get a plain center crop.
Respect EXIF orientation before resizingauto-orient
await sharp('camera-photo.jpg')
.autoOrient()
.resize({ width: 1200 })
.toFile('web.jpg');Without autoOrient(), phone photos come out sideways because EXIF rotation is metadata, not pixels; output EXIF orientation is reset for you.
Read dimensions and format without decoding pixelsread-metadata
const { width, height, format, hasAlpha } = await sharp('input.jpg').metadata();
console.log(width, height, format, hasAlpha);metadata() reads the header only, so it is cheap; width/height are pre-rotation values unless you check the orientation field.
Overlay a watermark in a cornercomposite-watermark
await sharp('photo.jpg')
.composite([{ input: 'logo.png', gravity: 'southeast' }])
.toFile('watermarked.jpg');composite() runs after resize in the pipeline, so the overlay is placed on the resized canvas; oversize overlays throw.
Process an upload stream without temp filesstream-pipeline
import { pipeline } from 'node:stream/promises';
const transformer = sharp().resize({ width: 1024 }).jpeg({ mozjpeg: true });
await pipeline(request, transformer, fs.createWriteStream('out.jpg'));sharp() with no input argument returns a duplex stream; errors surface on the pipeline promise, not the response.
Get the output Buffer plus final dimensionsbuffer-with-info
const { data, info } = await sharp(inputBuffer)
.resize({ width: 640 })
.png()
.toBuffer({ resolveWithObject: true });
console.log(info.width, info.height, info.size);info reflects the actual output after resize, useful when fit: 'inside' produced dimensions you did not specify.
Create a solid-color image from scratchgenerate-image
const png = await sharp({
create: {
width: 48,
height: 48,
channels: 4,
background: { r: 255, g: 0, b: 0, alpha: 0.5 }
}
}).png().toBuffer();You must call an output format method like .png(); raw created pixels have no default encoding.
Round corners using an SVG maskrounded-corners
const mask = Buffer.from(
'<svg><rect x="0" y="0" width="200" height="200" rx="50" ry="50"/></svg>'
);
await sharp('input.jpg')
.resize(200, 200)
.composite([{ input: mask, blend: 'dest-in' }])
.png()
.toFile('rounded.png');Output must be a format with alpha (PNG/WebP/AVIF); JPEG output flattens the transparency to black.
Smaller JPEGs with mozjpeg settingsoptimize-jpeg
await sharp('input.jpg')
.jpeg({ mozjpeg: true, quality: 78 })
.toFile('optimized.jpg');mozjpeg: true trades encode speed for smaller files; it is a preset of options, not a different encoder binary.
Encode AVIF for modern browsersconvert-to-avif
await sharp('input.jpg')
.avif({ quality: 50, effort: 4 })
.toFile('output.avif');AVIF encoding is much slower than WebP at higher effort values; quality numbers are not comparable across formats.