--- 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