mrkeyoor.com_
Sat 19 Sept 06:40 UTC
npmUtilsupdated 19 Sept 2026

sharp review

Our Node 22 sandbox loaded sharp 0.35.3 through both `require()` and ESM `import`, while the attempted esbuild browser bundle failed. Sharp is a Node-API binding to libvips that reads image paths, buffers, streams, raw pixels, or generated images, then resizes, crops, composites, changes colour, and encodes JPEG, PNG, WebP, GIF, AVIF, or TIFF output. Current version 0.35.4 caps resize and composite coordinates, rounds palette bit depth correctly, honours TIFF sub-IFDs, fixes limited-page counts, and accepts input streams that finish before output is requested.

90.9Mdownloads / wk
Verdict

Sharp 0.35.3 installed in 2.4 seconds and left 10 packages occupying 29 MB in our sandbox; both Node module loaders worked, npm audit found 0 known vulnerabilities, and the browser bundle failed. Install 0.35.4 for server image pipelines only when the build can retain the correct native package for its deployment target.

We installed it

Lab card: what happened when we installed sharpScreenshot of sharp documentation
Install✓ · 2.4s10 packages on disk · 29 MB · native build step
ImportESM import works · require() works · CommonJS package with exports map
Browsern/acould not be bundled for the browser (Node-only code, most likely)
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does sharp install cleanly?

Yes. In a fresh container with an empty cache, npm install sharp finished in 2 seconds, leaving 10 packages and 29 MB on disk, after a native build step. npm audit reported no known vulnerabilities.

Can sharp run in a browser?

Not directly: esbuild could not bundle it for the browser in our run, which normally means it depends on Node built-ins. Use it on the server, or find a browser-targeted alternative.

Does sharp work with both ESM and CommonJS?

Yes. Both import 'sharp' and require('sharp') worked in Node 22 in our run. The package is published as CommonJS with an exports map.

Does sharp include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

sharp or jimp: which should you use?

jimp: Choose it when your edits fit Jimp's JPEG, PNG, and GIF set and zero native dependencies matter more than AVIF output or libvips speed. Sharp 0.35.3 installed in 2.4 seconds and left 10 packages occupying 29 MB in our sandbox; both Node module loaders worked, npm audit found 0 known vulnerabilities, and the browser bundle failed.

When should you not use sharp?

The image code runs in a browser. Our esbuild browser build failed, and the official WebAssembly instructions state that browser use is unsupported.

API stability4/5Sharp 0.35.4 still accepts the established `sharp(input).resize(...).toBuffer()` chain and exposes matching CommonJS and ESM entry points. The method contract spells out order: a pipeline honours one resize, composite follows resize and extract, and output begins with a terminal call. Sharp remains below 1.0, so a new minor line can change the API under semantic versioning. Image fixtures are also worth running on patches because 0.35.4 changed edge handling for resize and overlay coordinates.
Docs5/5The official reference enumerates 5 resize fit modes and states that a pipeline honours 1 resize call. It also records defaults for input limits, cache size, each encoder, and metadata handling. Installation has dedicated recipes for npm, Yarn, pnpm, Bun, Deno, WebAssembly, Lambda, Electron, webpack, esbuild, and Vite. Most examples show the full transform chain. Platform and bundler warnings live on the install page, so method-level reading alone will miss deployment requirements.
Maintenance5/5GitHub reported 32,656 stars, 120 open issues and pull requests, an unarchived repository, and a push on September 7, 2026. Release 0.35.4 shipped on August 26 with fixes for coordinate bounds, palette bit depth, TIFF sub-IFDs, limited page counts, and early-finished streams. Repository work continued after that release. The maintained surface includes JavaScript, C++, platform binary packages, libvips integration, and bundled TypeScript declarations.
Ecosystem5/5npm recorded 73,954,756 sharp downloads for September 4 through September 10, 2026. The 0.35.4 manifest publishes optional binaries for common macOS, Windows, glibc Linux, and musl Linux targets, plus WebContainer and FreeBSD WebAssembly packages. The docs provide packaging recipes for Lambda and Electron as well. Every deployment still depends on the matching optional package; copying a dependency tree between unlike targets can leave out the binary the runtime needs.

Discussed on

  1. hnSharp: High performance Node.js image processing/optimization44 points

Use it if

  • A Node upload service must turn one photo into fixed crops, responsive widths, or modern formats without writing an intermediate file.
  • You receive buffers or streams and need one backpressured decode-transform-encode pipeline.
  • Your pipeline must apply EXIF orientation, control ICC colour conversion and alpha, or keep every animated GIF and WebP frame.
  • The deployment target has a supported Node-API runtime and can ship the optional binary chosen for its OS, CPU, and libc.
Skip it if

Setup reality

We installed sharp 0.35.3 with npm in a fresh, unprivileged Node 22 Bookworm container with 3 CPUs and 8 GB of RAM. Installation succeeded in 2.4 seconds after a native/compile step, leaving 10 packages and 29 MB on disk. The package itself had 3 direct dependencies, 0 peers, 1,048 KB unpacked, bundled TypeScript declarations, and an Apache-2.0 license. npm audit returned 0 findings across the four reported severity levels. Both require() and ESM import loaded; an esbuild browser build failed.

No account, API key, or config file is part of sharp 0.35.4. Installation is the configuration point: Node must be 20.9.0 or newer, optional dependencies must remain enabled, and the selected @img/* binary must match the production OS, CPU, and libc. npm offers --os, --cpu, and --libc for cross-target work, while Yarn and pnpm have supported-architecture settings. Exclude sharp from esbuild or webpack server bundles so Node can find its native package at runtime.

Sharp reads headers through metadata() without decoding compressed pixels, then performs the queued transforms when a terminal call such as toBuffer() or toFile() asks for output. The constructor reads 1 page by default, so set animated: true when every GIF or WebP frame belongs in the result. Default output strips EXIF and other metadata, including orientation; call autoOrient() before resizing and retain metadata only when the result needs it. For multiple sizes, clone one input and give each clone its own output chain.

libvips keeps a process-level operation cache; the documented defaults are 50 MB, 20 open files, and 100 cached operations. sharp.concurrency() controls native threads for each image, not the number of uploads your service accepts. On glibc Linux without jemalloc, sharp defaults that value to 1 to reduce allocator fragmentation, and AVIF encoders can create threads outside the setting. Put a bounded application queue in front of CPU-heavy requests, retain the input-pixel guard, and use stream.pipeline() so upstream and destination failures reject together.

Patterns

Resize a photo without enlarging it resize-photo

import sharp from 'sharp';

await sharp('source.jpg')
  .autoOrient()
  .resize({ width: 1600, withoutEnlargement: true })
  .jpeg({ quality: 82, mozjpeg: true })
  .toFile('photo-1600.jpg');

`withoutEnlargement` can produce an image narrower than 1,600 pixels when the source is smaller. `autoOrient()` applies the EXIF rotation before resize.

Crop a square thumbnail around image detail crop-thumbnail

await sharp(photoBuffer)
  .resize(360, 360, {
    fit: 'cover',
    position: sharp.strategy.attention,
  })
  .webp({ quality: 78 })
  .toBuffer();

`cover` crops to exactly 360 by 360 pixels. The attention strategy scores luminance, saturation, and skin-tone cues rather than using a fixed center crop.

Fit an image inside a fixed canvas pad-to-frame

await sharp(input)
  .resize({
    width: 1200,
    height: 630,
    fit: 'contain',
    background: { r: 248, g: 248, b: 248, alpha: 1 },
  })
  .png()
  .toFile('social-card.png');

`contain` preserves aspect ratio and fills unused pixels with the supplied background; the default background would be opaque black.

Encode a buffer and inspect the result convert-buffer

const { data, info } = await sharp(uploadBuffer)
  .autoOrient()
  .resize({ width: 960 })
  .avif({ quality: 55 })
  .toBuffer({ resolveWithObject: true });

console.log(info.format, info.width, info.height, info.size);

`resolveWithObject: true` returns encoded bytes in `data` and output facts in `info`; those dimensions describe the AVIF result, not the source header.

Read dimensions before decoding pixels inspect-metadata

const meta = await sharp(fileBuffer).metadata();

console.log({
  format: meta.format,
  width: meta.width,
  height: meta.height,
  orientation: meta.orientation,
  pages: meta.pages,
});

`metadata()` reads uncached headers without decoding compressed pixels. Width and height describe stored orientation unless you use its auto-oriented dimensions.

Convert every animation frame preserve-animation

await sharp(animatedGif, { animated: true })
  .resize({ width: 480 })
  .webp({ quality: 76 })
  .toFile('animation.webp');

The constructor reads 1 page by default; `animated: true` is equivalent to requesting all pages with `pages: -1`.

Place a watermark after resizing add-watermark

await sharp('original.jpg')
  .resize({ width: 1400 })
  .composite([{
    input: 'watermark.png',
    gravity: 'southeast',
    blend: 'over',
  }])
  .jpeg({ quality: 84 })
  .toFile('watermarked.jpg');

Composite runs after resize and extract, and the overlay must be no larger than the processed 1,400-pixel canvas at that stage.

Cut an avatar with an SVG mask round-avatar

const circle = Buffer.from(
  '<svg width="256" height="256"><circle cx="128" cy="128" r="128" fill="white"/></svg>',
);

await sharp(avatarBuffer)
  .resize(256, 256, { fit: 'cover' })
  .composite([{ input: circle, blend: 'dest-in' }])
  .png()
  .toFile('avatar.png');

The `dest-in` mask leaves transparent corners, so use PNG, WebP, or another alpha-capable output instead of JPEG.

Transform an upload with stream backpressure stream-upload

import { pipeline } from 'node:stream/promises';
import { createWriteStream } from 'node:fs';

await pipeline(
  requestStream,
  sharp().autoOrient().resize({ width: 1024 }).webp({ quality: 80 }),
  createWriteStream('upload.webp'),
);

Sharp is a Node Duplex stream. Awaiting `pipeline()` carries source, transform, and destination errors into one rejected promise.

Create two formats from one input generate-variants

const base = sharp(sourceBuffer).autoOrient();

const [smallWebp, largeAvif] = await Promise.all([
  base.clone().resize({ width: 480 }).webp({ quality: 78 }).toBuffer(),
  base.clone().resize({ width: 1280 }).avif({ quality: 55 }).toBuffer(),
]);

Each `clone()` inherits the same input and starts its own transform chain; one pipeline honours only its final resize call.

Cap untrusted image work bound-untrusted-input

const output = await sharp(uploadBuffer, {
  failOn: 'warning',
  limitInputPixels: 40_000_000,
})
  .resize({ width: 2000, withoutEnlargement: true })
  .timeout({ seconds: 5 })
  .webp()
  .toBuffer();

The documented default pixel limit is 268,402,689. Set a lower application limit and a processing timeout when uploads come from users.

Set process-wide cache and thread limits tune-native-work

sharp.cache({ memory: 32, files: 0, items: 64 });
sharp.concurrency(2);

console.log(sharp.cache());
console.log(sharp.counters());

`sharp.concurrency(2)` allows up to 2 libvips threads for each image; it does not cap simultaneous requests. The cache settings also affect the whole process.

Alternatives

PackageRegistryPick it when
jimpnpmChoose it when your edits fit Jimp's JPEG, PNG, and GIF set and zero native dependencies matter more than AVIF output or libvips speed.
@napi-rs/canvasnpmChoose it when Canvas 2D drawing, text, paths, and chart rendering are the main operation.
image-jsnpmChoose it for programmatic pixel analysis and image manipulation through a JavaScript API.
image-sizenpmChoose it when you only need image dimensions and will not transform the pixels.

More utils guides

lru-cache · type-fest · ajv · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.