# sharp: libvips behind a Node-API v9 chain, still on 0.x versioning

> sharp is a Node-API module that wraps libvips for image work inside Node, Deno and Bun, doing resizing, rotation, extraction, compositing and gamma correction in the same process as your JavaScript. The catch is versioning, since package.json sits at 0.35.5 and the release list shows two release candidates in the same window.

**lovell/sharp** — GitHub describes it as High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, AVIF and TIFF images. Uses the libvips library.. The repository metadata lists JavaScript as its primary language. The metadata lists the Apache-2.0 license. This article stays within the project description and details documented in the GitHub repository README.

- Repository: https://github.com/lovell/sharp
- Website: https://sharp.pixelplumbing.com
- Stars: 32,734 · Forks: 1,441
- Language: JavaScript
- License: Apache-2.0
- Published: 2026-08-13 · Updated: 2026-08-18 · Language: en
- Canonical page: https://hysenlabs.com/projects/lovell-sharp

## The API is a chain of calls that ends in toBuffer or toFile

Install is one command, and the readme's own example is the fastest way to see the shape of the API.

```sh
npm install sharp
```

The module is imported the two usual ways, ESM and CommonJS, which matters because a mixed-module codebase has to pick one.

```javascript
// ESM
import sharp from 'sharp';

// CJS
const sharp = require('sharp');
```

Everything after that is a chain on a value that starts from a file path, a buffer or a stream. The readme's first example takes an input buffer, resizes it, and writes a WebP file, and the callback signature `(err, info)` is the older Node style rather than a promise.

```javascript
await sharp(inputBuffer)
  .resize({ width: 320, height: 240 })
  .toFile('output.webp', (err, info) => { ... });
```

That is the module's whole design in four lines. Construction describes the source, each method configures one operation, and the terminal method decides whether the bytes come back in memory or land on disk. Nothing is processed until the last call, so the chain can be built up in a variable and finished somewhere else, which is what makes the stream form possible.

## sharp() with no input at all turns the module into a stream transform

The composite example is the one that tells you sharp is not only a file-to-file tool. It builds an SVG buffer by hand, passes it to `composite` with the blend mode `dest-in` to knock the corners out, and produces a resize transform with no source file anywhere in it.

```javascript
const roundedCorners = Buffer.from(
  '<svg><rect x="0" y="0" width="200" height="200" rx="50" ry="50"/></svg>'
);

const roundedCornerResizer =
  sharp()
    .resize(200, 200)
    .composite([{
      input: roundedCorners,
      blend: 'dest-in'
    }})
    .png();
```

The readme finishes that example by piping a readable stream into the transform and the transform into a writable stream, so the same object is a duplex pass-through. A request body goes in, a thumbnail comes out, with no temporary file in between.

The other direction is the same idea. Instead of a source, you can hand sharp a `create` block and it fabricates pixels, which the readme uses to make a 48 by 48 image with four channels and a half-transparent red background before encoding it as PNG. Composing from vector input, generating from nothing, and streaming all run through the same object.

## Node-API v9 is the contract, and it is what buys Deno and Bun

The compatibility claim is stated as a runtime feature rather than a Node version: sharp works with all JavaScript runtimes that provide support for Node-API v9, and the readme names Node.js at 20.9.0 and above, Deno and Bun.

Node-API is the stable binary interface that lets a native addon load across runtime versions without recompiling. That is the mechanism behind the runtime list. sharp is a native module, not a JavaScript package that happens to call out, and Node-API v9 is the specific feature level it binds against. The floor is therefore a property of the runtime, and the same binary works in three runtimes rather than three builds.

The practical consequence lands in CI. Your Node matrix cannot go below 20.9.0, and a runtime without Node-API v9 cannot load the module at all, which fails at require time with a native binding error rather than a helpful message. Anyone supporting a mixed-runtime fleet should check that each one advertises v9 before the dependency is added, because the failure appears the first time the import is evaluated.

There is a second consequence to keep in view. Because this is a compiled binding with a per-platform binary, a lockfile entry is not enough on its own. The install step has to actually run on the target platform, and that is where the platform story gets thin.

## Most modern systems need nothing extra, and the readme will not name which

The dependency claim is one sentence: most modern macOS, Windows and Linux systems do not require any additional install or runtime dependencies. There is no list of versions, no matrix of distributions, and no statement of what makes a system modern. The readme sends anything less obvious to a separate installation page on the project documentation site.

That is a defensible way to write a readme and an unhelpful way to plan a rollout. The word doing the work is most. The consequence is that the platforms which need extra steps are exactly the ones you cannot predict from the readme, and you find out during a container build on the one node that does not match the majority.

The repository layout shows where that logic lives without explaining it. There is an install/ directory, a patches/ directory, a lib/ directory and a src/ directory, alongside scripts/, npm/, test/ and docs/. Prebuilt platform binaries and any patching of upstream sources would sit in those directories rather than in JavaScript, and the readme does not say which platforms ship a prebuilt binary or what is compiled on your machine when none does.

So the honest version is: on a mainstream desktop or a current server Linux, expect `npm install sharp` to be the whole story. Anywhere else, budget a read of the install documentation before you promise the build will work unattended.

## The 4x-5x figure is measured against the quickest settings of a command-line tool

The performance claim has an unusually specific baseline. The readme says resizing an image is typically 4x-5x faster than using the quickest ImageMagick and GraphicsMagick settings, and attributes it to the use of libvips. Benchmark tests are published on the project's documentation site under a performance page.

Read the comparison carefully before you quote it. It is measured against the fastest settings those command-line tools can be configured for, not against their defaults, and the word typically is the project's own. That makes it a comparison against the best case of the alternative rather than the common case, which is the harder comparison to win and the more useful one to have.

What the number does not cover is anything but resizing. A service doing a hundred resizes an hour is the case the claim is about. It says nothing about throughput on a loaded machine, about the cost of decoding an unusual format, or about the overhead of the native binding, because none of those are in the sentence.

The resampling choice is named in the same breath as the speed claim: Lanczos, chosen so quality is not sacrificed for the speed. If your pipeline is resizing to a thumbnail, that pairing is the reason to consider this module. If your bottleneck is elsewhere, no multiplier will move it.

## Four operations are named, and no example draws text

Beyond resizing, the readme names rotation, extraction, compositing and gamma correction. Four, and that is the complete list of operations in the readme.

The second example shows two of them composed, and shows the encoder chosen at the end of the chain rather than at the start.

```javascript
const output = await sharp('input.jpg')
  .autoOrient()
  .resize({ width: 200 })
  .jpeg({ mozjpeg: true })
  .toBuffer();
```

`autoOrient` applies the EXIF rotation before the resize, which is the ordering that matters, and `mozjpeg` switches the JPEG encoder rather than just naming an output format. The output is a buffer, so nothing touches a filesystem in that path.

The gap to know about is what is not in the list. No example covers drawing text, drawing shapes, blurring or sharpening, and the vector input in the composite example arrives as a ready-made SVG buffer rather than something sharp renders. If your job is stamping a caption onto a photo, the readme gives you no path to it and the API documentation is where you would look. Three named colour guarantees sit alongside the list: colour spaces, embedded ICC profiles and alpha transparency channels.

Read as a whole, the module is scoped to transforming images that already exist, with a strong bias toward getting resizing right.

## 0.35.5 with two release candidates in the same window

package.json declares version 0.35.5, and the recent release list holds v0.35.5, v0.35.5-rc.1 and v0.35.5-rc.0, the last two dated within days of each other and of the final tag. The last push to main was on 2026-09-22.

Two things follow from a 0.x version, and both are about your dependency declaration rather than the code. Under semantic versioning the first digit is the compatibility signal, so a 0.36 release is allowed to break 0.35 consumers. A caret range on 0.35.5 does not protect a service from that, because the range stops at 0.36.0. Pin the exact version if a breaking jump would be an incident.

The release candidates are the second thing. An rc tag in the same window means someone, somewhere, ran a pre-release build, and dist-tags are the mechanism by which an rc reaches a plain install. If you pin a tag rather than a range, you control which artifact you get. If you track a dist-tag, check what it currently points at before a deploy rather than after.

The cost of getting this wrong is not subtle. This module is usually one line in a larger service, and a native binding that fails to load or returns different pixels is the kind of failure that surfaces in production images rather than in a test suite that never exercised the new version.

## Apache-2.0 on a personal copyright line, with libvips as the real dependency

The readme reproduces the Apache License 2.0 in full, including the AS IS warranty disclaimer, under a copyright line naming Lovell Fuller and others with the year 2013. There is no foundation or steering committee named, and package.json lists the author as Lovell Fuller with a long contributors list beneath. What that arrangement means for governance is a question the readme does not answer, and it is worth asking before you depend on a single maintainer's name.

Where the module stands against the command-line tools it names is easier. ImageMagick and GraphicsMagick are separate programs you invoke, parse output from and manage as dependencies of the host system. sharp is a library bound over libvips inside your process, which is why the resizing claim is about an in-process call rather than a subprocess. You get no shell, no temporary files and no per-call process startup, and you get a native binding that has to load on the platform.

That is the whole trade. Read the Apache 2.0 terms for redistribution obligations yourself, this is not legal advice, and remember that the licence covering the JavaScript is not the only licence in a build, because libvips is doing the actual pixel work underneath.

## Conclusion

Use sharp when a Node service has to resize or composite images in the same process as its JavaScript, and pin the exact version instead of a caret range, because 0.35.5 is what package.json declares and the release list shows two release candidates alongside it. Do not reach for it from a shell script that only converts files, since the readme compares it to command-line tools and the module ships no CLI of its own. Before committing, read the install page for your platform, because the no-extra-dependencies claim is scoped to most modern macOS, Windows and Linux systems and the readme does not name the ones outside that. For commercial use, read the Apache License 2.0 terms yourself, and note the copyright line names Lovell Fuller and others rather than a foundation.

## FAQ

### How do you install sharp in a Node.js project?

The readme gives one command, npm install sharp, and states that most modern macOS, Windows and Linux systems require no additional install or runtime dependencies. Anything outside that set is pointed at the installation page on the project documentation site.

### Which Node.js versions does sharp support?

It works with all JavaScript runtimes that provide support for Node-API v9, and the readme names Node.js 20.9.0 and above, Deno and Bun. The floor is a runtime interface level rather than a language version.

### Which image formats does sharp handle?

The stated use case is converting large images into smaller web-friendly JPEG, PNG, WebP, GIF and AVIF images of varying dimensions. The package description also names TIFF among the formats it resizes.

### Does sharp replace ImageMagick?

The readme compares resizing against the quickest ImageMagick and GraphicsMagick settings and credits libvips for the difference. sharp is an in-process Node-API module rather than a command-line program, so it replaces the subprocess call in a JavaScript service, not a shell pipeline.

### Can sharp generate an image instead of reading one?

Yes. The readme shows sharp taking a create block with a width, height, channel count and a background colour, then encoding the result to PNG, which is how the 48 by 48 half-transparent red example is produced.

## Sources

- [Official documentation](https://sharp.pixelplumbing.com)
- [Official README](https://github.com/lovell/sharp#readme)
- [Project repository](https://github.com/lovell/sharp)
- [Release notes](https://github.com/lovell/sharp/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/lovell-sharp
