---
name: sharp
description: Process images using the sharp Node.js library. Use when the user wants to resize, convert, crop, composite, transform, or optimize images programmatically.
---

# Sharp

API reference for sharp — a high-performance Node.js image processing library built on libvips.

Typically 4-5x faster than ImageMagick/GraphicsMagick. Supports JPEG, PNG, WebP, GIF, AVIF, TIFF, SVG, HEIF, JP2, and JXL.

Official docs: https://sharp.pixelplumbing.com
Repository: https://github.com/lovell/sharp

## Installation

```bash
npm install sharp
```

Requires Node.js ^18.17.0 or >= 20.3.0 (or Deno/Bun with Node-API v9).

## Usage

Sharp uses a fluent, chainable API. Every call returns a Sharp instance.

```js
import sharp from 'sharp';

await sharp('input.jpg')
  .resize(800, 600)
  .jpeg({ quality: 80 })
  .toFile('output.jpg');
```

Sharp implements `stream.Duplex` — it can be piped to/from.

---

## Constructor

```js
sharp([input], [options])
```

- `input` (Buffer | string | Array): Image buffer, file path, array of inputs, or omit for stream input.

### Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `failOn` | string | `'warning'` | `'none'`, `'truncated'`, `'error'`, `'warning'` |
| `limitInputPixels` | number \| boolean | `268402689` | Max pixels; `false` to disable |
| `unlimited` | boolean | `false` | Remove memory safety for JPEG/PNG/SVG/HEIF |
| `autoOrient` | boolean | `false` | Auto-rotate per EXIF Orientation |
| `sequentialRead` | boolean | `true` | Sequential vs random access |
| `density` | number | `72` | DPI for vector images (1-100000) |
| `ignoreIcc` | boolean | `false` | Ignore embedded ICC profile |
| `pages` | number | `1` | Pages to extract; `-1` for all |
| `page` | number | `0` | Starting page (zero-based) |
| `animated` | boolean | `false` | Read all frames (equiv. `pages: -1`) |

### Raw Input

```js
sharp(buffer, { raw: { width: 100, height: 100, channels: 4 } })
```

| Property | Type | Description |
|----------|------|-------------|
| `width` | number | Pixel width |
| `height` | number | Pixel height |
| `channels` | number | 1-4 |
| `premultiplied` | boolean | Skip premultiplication (default `false`) |

### Create New Image

```js
sharp({ create: { width: 300, height: 200, channels: 4, background: '#ff0000' } })
```

| Property | Type | Description |
|----------|------|-------------|
| `width` | number | Pixel width |
| `height` | number | Pixel height |
| `channels` | number | 3 (RGB) or 4 (RGBA) |
| `background` | string \| Object | Color (parsed by color module) |
| `noise` | Object | `{ type: 'gaussian', mean: 128, sigma: 30 }` |

### Render Text

```js
sharp({ text: { text: 'Hello', font: 'Arial', dpi: 150 } })
```

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `text` | string | -- | UTF-8; supports Pango markup |
| `font` | string | -- | Font name |
| `fontfile` | string | -- | Absolute path to font file |
| `width` | number | `0` | Word-wrap boundary; 0 = no wrap |
| `height` | number | `0` | Max height |
| `align` | string | `'left'` | `'left'`, `'centre'`, `'center'`, `'right'` |
| `justify` | boolean | `false` | Text justification |
| `dpi` | number | `72` | Render resolution |
| `rgba` | boolean | `false` | RGBA for color emoji/Pango markup |
| `spacing` | number | `0` | Line height in points |
| `wrap` | string | `'word'` | `'word'`, `'char'`, `'word-char'`, `'none'` |

### Join Array

```js
sharp([img1, img2, img3], { join: { across: 3, shim: 10 } })
```

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `across` | number | `1` | Images per row |
| `animated` | boolean | `false` | Join as animated image |
| `shim` | number | `0` | Pixel gap between images |
| `background` | string \| Object | -- | Gap fill color |
| `halign` | string | `'left'` | `'left'`, `'centre'`, `'right'` |
| `valign` | string | `'top'` | `'top'`, `'centre'`, `'bottom'` |

### Clone

```js
const pipeline = sharp('input.jpg');
const clone1 = pipeline.clone().resize(200).toFile('thumb.jpg');
const clone2 = pipeline.clone().resize(800).toFile('large.jpg');
```

---

## Resize

```js
.resize([width], [height], [options])
```

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `width` | number | -- | Target width (null to auto-scale) |
| `height` | number | -- | Target height (null to auto-scale) |
| `fit` | string | `'cover'` | `'cover'`, `'contain'`, `'fill'`, `'inside'`, `'outside'` |
| `position` | string | `'centre'` | Gravity/position for cover/contain |
| `background` | string \| Object | `{r:0,g:0,b:0,alpha:1}` | Fill color for `contain` |
| `kernel` | string | `'lanczos3'` | `'nearest'`, `'linear'`, `'cubic'`, `'mitchell'`, `'lanczos2'`, `'lanczos3'` |
| `withoutEnlargement` | boolean | `false` | Don't upscale |
| `withoutReduction` | boolean | `false` | Don't downscale |
| `fastShrinkOnLoad` | boolean | `true` | JPEG/WebP shrink-on-load |

**Fit modes:**
- `cover` — crop to fill both dimensions
- `contain` — letterbox within dimensions
- `fill` — stretch to exact dimensions (ignores aspect ratio)
- `inside` — fit within, no exceeding
- `outside` — minimum size meeting both dimensions

**Position values:** `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`, `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `centre`/`center`

**Strategy (cover only):** `entropy`, `attention`

Only one resize per pipeline.

---

## Operations

### Rotation & Orientation

| Method | Description |
|--------|-------------|
| `.rotate([angle], [options])` | Rotate by degrees; omit angle for EXIF auto-rotate. `options.background` for fill color |
| `.autoOrient()` | Auto-orient from EXIF, then remove Orientation tag |
| `.flip([flip])` | Vertical mirror (default `true`) |
| `.flop([flop])` | Horizontal mirror (default `true`) |

### Transform

| Method | Description |
|--------|-------------|
| `.affine(matrix, [options])` | 2x2 affine transform. Options: `background`, `idx`, `idy`, `odx`, `ody`, `interpolator` |
| `.extend(extend)` | Add padding. Number for uniform, or `{ top, right, bottom, left, extendWith, background }`. `extendWith`: `'background'`, `'copy'`, `'repeat'`, `'mirror'` |
| `.extract({ left, top, width, height })` | Crop region. Can be called before and/or after resize |
| `.trim([options])` | Auto-crop to content. Options: `background` (default top-left pixel), `threshold` (default `10`), `lineArt` |

### Enhancement

| Method | Description |
|--------|-------------|
| `.sharpen([options])` | Sharpen. `options.sigma` (0.000001-10), `.m1` (flat), `.m2` (jagged), `.x1`, `.y2`, `.y3` |
| `.blur([options])` | No args: 3x3 box blur. `options.sigma` (0.3-1000) for Gaussian. Options: `precision`, `minAmplitude` |
| `.median([size])` | Median filter, default 3x3 |
| `.gamma([gamma], [gammaOut])` | Gamma correction (1.0-3.0, default 2.2) |
| `.normalise([options])` | Stretch luminance. `options.lower` (default `1`), `.upper` (default `99`) percentiles |
| `.clahe({ width, height, [maxSlope] })` | Contrast Limited Adaptive Histogram Equalization |

### Morphology

| Method | Description |
|--------|-------------|
| `.dilate([width])` | Dilation, default 1px |
| `.erode([width])` | Erosion, default 1px |

### Pixel Operations

| Method | Description |
|--------|-------------|
| `.negate([options])` | Invert colors. `options.alpha` (default `true`) |
| `.threshold([value], [options])` | Binarize at threshold (0-255, default 128). `options.greyscale` (default `true`) |
| `.boolean(operand, operator)` | Bitwise op with another image: `'and'`, `'or'`, `'eor'` |
| `.linear([a], [b])` | Per-channel linear transform: `a * pixel + b` |
| `.recomb(matrix)` | 3x3 or 4x4 color recombination matrix |
| `.modulate([options])` | Adjust `brightness` (multiply), `saturation` (multiply), `hue` (degrees), `lightness` (add) |
| `.convolve(kernel)` | Custom convolution: `{ width, height, kernel, scale, offset }` |
| `.flatten([options])` | Merge alpha with `options.background`, remove alpha |
| `.unflatten()` | Add alpha; white becomes transparent (experimental) |

---

## Colour

| Method | Description |
|--------|-------------|
| `.tint(color)` | Apply tint, preserving alpha |
| `.greyscale([bool])` | Convert to 8-bit greyscale (alias: `.grayscale()`) |
| `.pipelineColourspace(space)` | Set pipeline colorspace (e.g. `'rgb16'`, `'lab'`, `'grey16'`) |
| `.toColourspace(space)` | Set output colorspace (e.g. `'srgb'`, `'cmyk'`, `'b-w'`) |

---

## Channel

| Method | Description |
|--------|-------------|
| `.removeAlpha()` | Remove alpha channel |
| `.ensureAlpha([alpha])` | Add alpha if missing. `alpha`: 0 (transparent) to 1 (opaque, default) |
| `.extractChannel(channel)` | Extract single channel: `0`-`3` or `'red'`, `'green'`, `'blue'`, `'alpha'` |
| `.joinChannel(images, [options])` | Add channel(s) from other image(s) |
| `.bandbool(op)` | Bitwise across all bands: `'and'`, `'or'`, `'eor'` |

---

## Composite

```js
.composite(images)
```

Overlay images onto the pipeline image. `images` is an array of objects:

| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `input` | Buffer \| string | -- | Image data, file path, or `create`/`text` object |
| `blend` | string | `'over'` | Blend mode |
| `gravity` | string | `'centre'` | Placement gravity |
| `top` | number | -- | Pixel offset from top (overrides gravity) |
| `left` | number | -- | Pixel offset from left (overrides gravity) |
| `tile` | boolean | `false` | Repeat overlay across image |
| `premultiplied` | boolean | `false` | Skip premultiplication |
| `density` | number | `72` | DPI for vector overlays |

**Blend modes:** `over`, `multiply`, `screen`, `overlay`, `darken`, `lighten`, `hard-light`, `soft-light`, `difference`, `exclusion`, `colour-dodge`, `colour-burn`, `add`, `saturate`, `clear`, `source`, `in`, `out`, `atop`, `dest`, `dest-over`, `dest-in`, `dest-out`, `dest-atop`, `xor`

```js
await sharp('base.png')
  .composite([{ input: 'overlay.png', gravity: 'southeast' }])
  .toFile('output.png');
```

---

## Output

### Write to File

```js
await sharp('input.jpg').resize(800).toFile('output.jpg');
```

Format inferred from extension. Returns `{ format, size, width, height, channels, premultiplied }`.

### Write to Buffer

```js
const buffer = await sharp('input.jpg').resize(800).toBuffer();
// or with info:
const { data, info } = await sharp('input.jpg').resize(800).toBuffer({ resolveWithObject: true });
```

### Format Methods

#### JPEG

```js
.jpeg([options])
```

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `quality` | number | `80` | 1-100 |
| `progressive` | boolean | `false` | Progressive JPEG |
| `chromaSubsampling` | string | `'4:2:0'` | `'4:2:0'` or `'4:4:4'` |
| `mozjpeg` | boolean | `false` | MozJPEG optimizations |
| `force` | boolean | `true` | Force JPEG output |

#### PNG

```js
.png([options])
```

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `progressive` | boolean | `false` | Progressive (interlace) |
| `compressionLevel` | number | `6` | 0-9 |
| `adaptiveFiltering` | boolean | `false` | Adaptive row filtering |
| `palette` | boolean | `false` | Quantise to palette |
| `quality` | number | `100` | Palette quality (1-100) |
| `effort` | number | `7` | CPU effort (1-10, palette mode) |
| `colours`/`colors` | number | `256` | Max palette colors (2-256) |
| `dither` | number | `1.0` | Floyd-Steinberg dithering level |
| `force` | boolean | `true` | Force PNG output |

#### WebP

```js
.webp([options])
```

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `quality` | number | `80` | 1-100 |
| `alphaQuality` | number | `100` | 0-100 |
| `lossless` | boolean | `false` | Lossless compression |
| `nearLossless` | boolean | `false` | Near-lossless mode |
| `smartSubsample` | boolean | `false` | Smart chroma subsampling |
| `preset` | string | `'default'` | `'default'`, `'photo'`, `'picture'`, `'drawing'`, `'icon'`, `'text'` |
| `effort` | number | `4` | 0-6 |
| `loop` | number | `0` | Animation loops (0 = infinite) |
| `delay` | number \| Array | -- | Frame delay(s) in ms |
| `force` | boolean | `true` | Force WebP output |

#### AVIF

```js
.avif([options])
```

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `quality` | number | `50` | 1-100 |
| `lossless` | boolean | `false` | Lossless mode |
| `effort` | number | `4` | 0-9 |
| `chromaSubsampling` | string | `'4:4:4'` | Chroma subsampling |
| `bitdepth` | number | `8` | 8, 10, or 12 |

#### GIF

```js
.gif([options])
```

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `reuse` | boolean | `true` | Reuse palette |
| `progressive` | boolean | `false` | Progressive (interlace) |
| `colours`/`colors` | number | `256` | 2-256 |
| `effort` | number | `7` | 1-10 |
| `dither` | number | `1.0` | 0-1 |
| `loop` | number | `0` | 0 = infinite |
| `delay` | number \| Array | -- | Frame delay(s) in ms |
| `force` | boolean | `true` | Force GIF output |

#### TIFF

```js
.tiff([options])
```

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `quality` | number | `80` | 1-100 |
| `compression` | string | `'jpeg'` | `'none'`, `'jpeg'`, `'deflate'`, `'packbits'`, `'lzw'`, `'webp'`, `'zstd'`, `'jp2k'`, `'ccittfax4'` |
| `predictor` | string | `'horizontal'` | `'none'`, `'horizontal'`, `'float'` |
| `pyramid` | boolean | `false` | Write image pyramid |
| `tile` | boolean | `false` | Tiled TIFF |
| `tileWidth` | number | `256` | Tile width |
| `tileHeight` | number | `256` | Tile height |
| `bitdepth` | number | `8` | 1, 2, 4, or 8 |
| `force` | boolean | `true` | Force TIFF output |

#### HEIF

```js
.heif({ compression: 'hevc' })
```

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `compression` | string | required | `'av1'` or `'hevc'` |
| `quality` | number | `50` | 1-100 |
| `lossless` | boolean | `false` | Lossless mode |
| `effort` | number | `4` | 0-9 |
| `bitdepth` | number | `8` | 8, 10, or 12 |

#### Raw

```js
.raw([options])
```

- `options.depth` (string, default `'uchar'`): `'char'`, `'uchar'`, `'short'`, `'ushort'`, `'int'`, `'uint'`, `'float'`, `'double'`

#### Tile (DZI / Zoomify / IIIF)

```js
.tile([options])
```

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `size` | number | `256` | Tile size (1-8192) |
| `overlap` | number | `0` | Tile overlap (0-8192) |
| `layout` | string | `'dz'` | `'dz'`, `'iiif'`, `'iiif3'`, `'zoomify'`, `'google'` |
| `container` | string | `'fs'` | `'fs'` or `'zip'` |
| `angle` | number | `0` | Rotation (multiple of 90) |
| `background` | string \| Object | white | Fill color |

---

## Metadata & Stats

### metadata()

```js
const meta = await sharp('input.jpg').metadata();
```

Returns without decoding pixels:

| Property | Type | Description |
|----------|------|-------------|
| `format` | string | `'jpeg'`, `'png'`, `'webp'`, `'gif'`, `'svg'`, etc. |
| `width` | number | Pixel width |
| `height` | number | Pixel height |
| `space` | string | Color space (`'srgb'`, `'rgb'`, `'cmyk'`, `'b-w'`, etc.) |
| `channels` | number | Band count |
| `depth` | string | Pixel depth (`'uchar'`, `'ushort'`, `'float'`, etc.) |
| `density` | number | DPI |
| `chromaSubsampling` | string | e.g. `'4:2:0'` |
| `isProgressive` | boolean | Progressive/interlaced |
| `hasAlpha` | boolean | Has alpha channel |
| `hasProfile` | boolean | Has ICC profile |
| `orientation` | number | EXIF orientation (1-8) |
| `pages` | number | Page count |
| `size` | number | Total bytes (Buffer/Stream input) |
| `exif` | Buffer | Raw EXIF |
| `icc` | Buffer | ICC profile |
| `xmp` | Buffer | XMP data |

### stats()

```js
const stats = await sharp('input.jpg').stats();
```

Returns pixel-derived statistics:

| Property | Type | Description |
|----------|------|-------------|
| `channels` | Array | Per-channel: `min`, `max`, `sum`, `mean`, `stdev`, `minX`, `minY`, `maxX`, `maxY` |
| `isOpaque` | boolean | Fully opaque |
| `entropy` | number | Greyscale entropy |
| `sharpness` | number | Laplacian sharpness |
| `dominant` | Object | Dominant sRGB color |

---

## Metadata Preservation

By default, sharp strips all metadata and converts to sRGB.

| Method | Description |
|--------|-------------|
| `.keepMetadata()` | Preserve all metadata (EXIF, ICC, XMP, IPTC) |
| `.keepExif()` | Preserve EXIF only |
| `.withExif(exif)` | Set EXIF (replaces input). Object keyed by IFD |
| `.withExifMerge(exif)` | Merge with existing EXIF |
| `.keepIccProfile()` | Preserve ICC profile |
| `.withIccProfile(icc, [options])` | Set ICC: path or `'srgb'`, `'p3'`, `'cmyk'` |
| `.keepXmp()` | Preserve XMP |
| `.withXmp(xmp)` | Set XMP (XML string) |
| `.withMetadata([options])` | Preserve most metadata. Options: `orientation`, `density` |

---

## Timeout

```js
.timeout({ seconds: 30 })
```

Abort processing after N seconds. `0` = no timeout (default).

---

## Utility (Static)

| Property/Method | Description |
|-----------------|-------------|
| `sharp.format` | Object with available input/output format booleans |
| `sharp.versions` | Version info for sharp, libvips, dependencies |
| `sharp.interpolators` | Enum: `nearest`, `bilinear`, `bicubic`, `lbb`, `nohalo`, `vsqbs` |
| `sharp.cache([options])` | Get/set cache: `{ memory: 50, files: 20, items: 100 }` |
| `sharp.concurrency([n])` | Get/set thread count (default: CPU cores) |
| `sharp.counters()` | Returns `{ queue, process }` |
| `sharp.simd([bool])` | Enable/disable SIMD (default `true`) |
| `sharp.block({ operation })` | Block specific operations |
| `sharp.unblock({ operation })` | Unblock operations |

---

## Common Recipes

### Resize and convert format

```js
await sharp('input.png')
  .resize(800, 600)
  .webp({ quality: 80 })
  .toFile('output.webp');
```

### Resize to fit within bounds (no upscale)

```js
await sharp('input.jpg')
  .resize(1200, 800, { fit: 'inside', withoutEnlargement: true })
  .toFile('output.jpg');
```

### Create thumbnail (cover crop)

```js
await sharp('input.jpg')
  .resize(250, 250, { fit: 'cover', position: 'attention' })
  .toFile('thumb.jpg');
```

### Crop region

```js
await sharp('input.jpg')
  .extract({ left: 100, top: 50, width: 400, height: 300 })
  .toFile('cropped.jpg');
```

### Add watermark overlay

```js
await sharp('photo.jpg')
  .composite([{ input: 'watermark.png', gravity: 'southeast' }])
  .toFile('watermarked.jpg');
```

### Composite text overlay

```js
await sharp('photo.jpg')
  .composite([{
    input: { text: { text: 'Hello World', font: 'sans', dpi: 200, rgba: true } },
    gravity: 'south'
  }])
  .toFile('annotated.jpg');
```

### Convert to greyscale

```js
await sharp('input.jpg')
  .greyscale()
  .toFile('grey.jpg');
```

### Blur

```js
await sharp('input.jpg')
  .blur({ sigma: 5 })
  .toFile('blurred.jpg');
```

### Rotate

```js
await sharp('input.jpg')
  .rotate(90)
  .toFile('rotated.jpg');
```

### Auto-orient from EXIF

```js
await sharp('input.jpg')
  .autoOrient()
  .toFile('oriented.jpg');
```

### Extend with padding

```js
await sharp('input.png')
  .extend({ top: 20, bottom: 20, left: 20, right: 20, background: '#ffffff' })
  .toFile('padded.png');
```

### Auto-trim whitespace

```js
await sharp('input.png')
  .trim({ threshold: 10 })
  .toFile('trimmed.png');
```

### Optimize JPEG for web

```js
await sharp('input.jpg')
  .resize(1920, null, { withoutEnlargement: true })
  .jpeg({ quality: 75, mozjpeg: true, progressive: true })
  .toFile('optimized.jpg');
```

### Generate AVIF from JPEG

```js
await sharp('input.jpg')
  .avif({ quality: 50, effort: 4 })
  .toFile('output.avif');
```

### Extract channel

```js
await sharp('input.png')
  .extractChannel('red')
  .toFile('red-channel.png');
```

### Get image metadata

```js
const { width, height, format, space } = await sharp('input.jpg').metadata();
```

### Buffer round-trip

```js
const buffer = await sharp('input.jpg')
  .resize(300)
  .png()
  .toBuffer();
```

### Create solid color image

```js
await sharp({ create: { width: 100, height: 100, channels: 4, background: '#ff6600' } })
  .png()
  .toFile('orange.png');
```

### Join images into grid

```js
await sharp(['a.png', 'b.png', 'c.png', 'd.png'], { join: { across: 2 } })
  .toFile('grid.png');
```

### Preserve metadata

```js
await sharp('input.jpg')
  .resize(800)
  .keepMetadata()
  .toFile('output.jpg');
```

### Animated GIF resize

```js
await sharp('input.gif', { animated: true })
  .resize(200)
  .gif()
  .toFile('small.gif');
```

### Multiple outputs from one input

```js
const pipeline = sharp('input.jpg');
await Promise.all([
  pipeline.clone().resize(200).toFile('thumb.jpg'),
  pipeline.clone().resize(800).toFile('medium.jpg'),
  pipeline.clone().resize(1600).toFile('large.jpg'),
]);
```

---

## Source

- Repository: https://github.com/lovell/sharp
- Documentation: https://sharp.pixelplumbing.com
- API — Constructor: https://sharp.pixelplumbing.com/api-constructor
- API — Input: https://sharp.pixelplumbing.com/api-input
- API — Resize: https://sharp.pixelplumbing.com/api-resize
- API — Operations: https://sharp.pixelplumbing.com/api-operation
- API — Colour: https://sharp.pixelplumbing.com/api-colour
- API — Channel: https://sharp.pixelplumbing.com/api-channel
- API — Composite: https://sharp.pixelplumbing.com/api-composite
- API — Output: https://sharp.pixelplumbing.com/api-output
- API — Utility: https://sharp.pixelplumbing.com/api-utility
