| name | sharp |
| description | Processes images with Sharp, the high-performance Node.js library for resizing, converting, and optimizing images. Use when building image pipelines, generating thumbnails, batch processing client photos, or optimizing uploads server-side.
Use when: resizing photos, converting to WebP/AVIF, batch optimization, thumbnail generation, watermarks. Errors: "Input file is missing", "unsupported image format", "memory allocation failed", "sharp: Installation failed".
|
| license | Apache-2.0 |
| metadata | {"version":"0.34.5","last_verified":"2026-01-05T00:00:00.000Z","packages":["sharp@0.34.5"],"node_version":"^18.17.0 || >= 20.3.0"} |
| source | plugin |
Sharp
High-performance Node.js image processing. 4-5x faster than ImageMagick for resizing JPEG, PNG, WebP, GIF, AVIF, and TIFF images. Uses libvips under the hood.
Supported Runtimes: Node.js (^18.17.0 or >= 20.3.0), Deno, Bun
Quick Start
npm install sharp
import sharp from 'sharp';
await sharp('input.jpg')
.resize(800, 600)
.toFormat('webp')
.toFile('output.webp');
const buffer = await sharp(inputBuffer)
.resize(400)
.toBuffer();
Constructor Options
const image = sharp('input.jpg', {
animated: true,
limitInputPixels: 268402689,
failOn: 'warning',
density: 300,
pages: -1,
page: 0,
});
const raw = sharp(buffer, {
raw: {
width: 800,
height: 600,
channels: 4
}
});
const blank = sharp({
create: {
width: 800,
height: 600,
channels: 4,
background: { r: 255, g: 255, b: 255, alpha: }
}
});
Resize
await sharp('input.jpg')
.resize(800, 600)
.toFile('output.jpg');
await sharp('input.jpg')
.resize(800, 600, { fit: 'inside' })
.toFile('output.jpg');
await sharp('input.jpg')
.resize(800, 600, { fit: 'cover' })
.toFile('output.jpg');
await sharp('input.jpg')
.resize({ width: 800 })
.toFile('output.jpg');
await sharp('input.jpg')
.resize(2000, null, { withoutEnlargement: true })
.toFile('output.jpg');
()
.(, , { : })
.();
Fit Options
| Option | Description |
|---|
cover | Crop to cover dimensions (default) |
contain | Fit within, add background if needed |
fill | Stretch to fill (ignores aspect ratio) |
inside | Fit within, never exceed dimensions |
outside | Fit to cover, may exceed one dimension |
Position (for cover/contain)
await sharp('input.jpg')
.resize(800, 600, {
fit: 'cover',
position: 'top'
})
.toFile('output.jpg');
await sharp('input.jpg')
.resize(800, 600, {
fit: 'cover',
position: sharp.strategy.entropy
})
.toFile('output.jpg');
await sharp('input.jpg')
.resize(800, 600, {
fit: 'cover',
position: sharp.strategy.attention
})
.toFile('output.jpg');
Resize Kernels
await sharp('input.jpg')
.resize(800, 600, {
kernel: 'lanczos3'
})
.toFile('output.jpg');
Format Conversion
await sharp('input.jpg')
.webp({ quality: 80 })
.toFile('output.webp');
await sharp('input.jpg')
.avif({ quality: 60 })
.toFile('output.avif');
await sharp('input.jpg')
.png({ compressionLevel: 9 })
.toFile('output.png');
await sharp('input.png')
.jpeg({ quality: 80, mozjpeg: true })
.toFile('output.jpg');
await sharp('input.jpg')
.gif()
.toFile('output.gif');
await sharp('input.jpg')
.heif({ quality: 80, compression: })
.();
()
.(, { : })
.();
Format Options
.jpeg({
quality: 80,
progressive: true,
mozjpeg: true,
chromaSubsampling: '4:4:4',
trellisQuantisation: true,
overshootDeringing: true,
})
.png({
compressionLevel: 9,
palette: true,
quality: 80,
colors: 256,
dither: 1.0,
})
.webp({
quality: 80,
lossless: false,
nearLossless: false,
effort: 4,
loop: 0,
: ,
})
.({
: ,
: ,
: ,
: ,
})
.({
: ,
: ,
: ,
: ,
: ,
})
.({
: ,
: ,
: ,
: ,
: ,
: ,
})
Animated Images (GIF/WebP)
const image = sharp('animated.gif', { animated: true });
const metadata = await image.metadata();
console.log(`Frames: ${metadata.pages}, Delay: ${metadata.delay}`);
await sharp('animated.gif', { animated: true })
.resize(400)
.gif()
.toFile('resized.gif');
await sharp('animated.gif', { animated: true })
.webp({ loop: 0 })
.toFile('animated.webp');
await sharp('animated.gif', { pages: 1, page: 5 })
.toFile('frame5.png');
Metadata
const metadata = await sharp('input.jpg').metadata();
console.log(metadata);
const stats = await sharp('input.jpg').stats();
console.log(stats);
await sharp('input.jpg')
.keepMetadata()
.resize(800)
.toFile('output.jpg');
await sharp('input.jpg')
.withMetadata({
orientation: 1,
density: 300,
exif: { IFD0: { Copyright: 'My Company' } }
})
.toFile('output.jpg');
Operations
Crop/Extract
await sharp('input.jpg')
.extract({ left: 100, top: 100, width: 300, height: 200 })
.toFile('output.jpg');
await sharp('input.jpg')
.trim()
.toFile('output.jpg');
await sharp('input.jpg')
.trim({ threshold: 10 })
.toFile('output.jpg');
Rotate & Flip
await sharp('input.jpg')
.rotate(90)
.toFile('output.jpg');
await sharp('input.jpg')
.rotate(45, { background: { r: 255, g: 255, b: 255 } })
.toFile('output.jpg');
await sharp('input.jpg')
.flip()
.flop()
.toFile('output.jpg');
await sharp('input.jpg')
.rotate()
.toFile('output.jpg');
Color Adjustments
await sharp('input.jpg')
.grayscale()
.toFile('output.jpg');
await sharp('input.jpg')
.tint({ r: 255, g: 200, b: 200 })
.toFile('output.jpg');
await sharp('input.jpg')
.modulate({
brightness: 1.2,
saturation: 0.8,
hue: 180,
lightness: 10,
})
.toFile('output.jpg');
await sharp('input.jpg')
.negate()
.toFile('output.jpg');
await sharp('input.jpg')
.negate({ alpha: false })
.toFile('output.jpg');
Effects
await sharp('input.jpg')
.blur(5)
.toFile('output.jpg');
await sharp('input.jpg')
.sharpen()
.toFile('output.jpg');
await sharp('input.jpg')
.sharpen({
sigma: 1,
m1: 1,
m2: 3,
x1: 2,
y2: 10,
y3: 20,
})
.toFile('output.jpg');
await sharp('input.jpg')
.normalize()
.toFile('output.jpg');
await sharp('input.jpg')
.()
.();
()
.()
.(, )
.();
()
.([
[, , ],
[, , ],
[, , ],
])
.();
Composite (Overlays/Watermarks)
await sharp('input.jpg')
.composite([
{
input: 'watermark.png',
gravity: 'southeast',
blend: 'over',
}
])
.toFile('output.jpg');
await sharp('base.jpg')
.composite([
{ input: 'layer1.png', top: 0, left: 0 },
{ input: 'layer2.png', top: 100, left: 100, blend: 'multiply' },
{
input: Buffer.from('<svg>...</svg>'),
top: 50,
left: 50,
}
])
.toFile('output.jpg');
const textSvg = `
<svg width="400" height="50">
<text x="0" y="35" font-size="30" fill="white">© My Company</text>
</svg>
`;
await sharp('input.jpg')
.composite([
{
input: Buffer.(textSvg),
: ,
}
])
.();
Add Background/Extend
await sharp('input.png')
.extend({
top: 20,
bottom: 20,
left: 20,
right: 20,
background: { r: 255, g: 255, b: 255, alpha: 1 }
})
.toFile('output.png');
await sharp('input.png')
.extend({
top: 50,
background: { r: 0, g: 0, b: 0 },
extendWith: 'mirror'
})
.toFile('output.png');
await sharp('input.png')
.flatten({ background: '#ffffff' })
.toFile('output.jpg');
Pipeline Chaining
await sharp('input.jpg')
.resize(800, 600, { fit: 'cover' })
.rotate(90)
.sharpen()
.modulate({ brightness: 1.1 })
.webp({ quality: 80 })
.toFile('output.webp');
Clone for Parallel Processing
const pipeline = sharp('input.jpg');
const [thumb, medium, large] = await Promise.all([
pipeline.clone().resize(150, 150).toBuffer(),
pipeline.clone().resize(400).toBuffer(),
pipeline.clone().resize(1200).toBuffer(),
]);
Streams & Buffers
import fs from 'fs';
const readStream = fs.createReadStream('input.jpg');
const writeStream = fs.createWriteStream('output.webp');
readStream
.pipe(sharp().resize(800).webp())
.pipe(writeStream);
const inputBuffer = fs.readFileSync('input.jpg');
const outputBuffer = await sharp(inputBuffer)
.resize(400)
.toBuffer();
const { data, info } = await sharp(inputBuffer)
.resize(400)
.toBuffer({ resolveWithObject: true });
console.log(info);
Next.js / API Routes
import { NextRequest, NextResponse } from 'next/server';
import sharp from 'sharp';
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const url = searchParams.get('url');
const width = parseInt(searchParams.get('w') || '800');
const quality = parseInt(searchParams.get('q') || '80');
const response = await fetch(url!);
const buffer = Buffer.from(await response.arrayBuffer());
const processed = await sharp(buffer)
.resize(width)
.webp({ quality })
.toBuffer();
return new (processed, {
: {
: ,
: ,
},
});
}
Generate Thumbnails
import sharp from 'sharp';
import path from 'path';
async function generateThumbnails(inputPath, outputDir) {
const sizes = [
{ name: 'thumb', width: 150, height: 150 },
{ name: 'small', width: 400 },
{ name: 'medium', width: 800 },
{ name: 'large', width: 1200 },
];
const basename = path.basename(inputPath, path.extname(inputPath));
const pipeline = sharp(inputPath);
const results = await Promise.all(
sizes.map(async ({ name, width, height }) => {
const outputPath = path.join(outputDir, `${basename}-${name}.webp`);
const info = await pipeline
.clone()
.resize(width, height, {
fit: height ? 'cover' : ,
:
})
.({ : })
.(outputPath);
{ name, : outputPath, ...info };
})
);
results;
}
Handle Uploads
import formidable from 'formidable';
import sharp from 'sharp';
async function handleUpload(req) {
const form = formidable();
const [fields, files] = await form.parse(req);
const file = files.image[0];
const metadata = await sharp(file.filepath).metadata();
if (!['jpeg', 'png', 'webp', 'gif'].includes(metadata.format)) {
throw new Error('Invalid format');
}
const filename = `${Date.now()}-${file.originalFilename}`;
const processed = await sharp(file.filepath)
.resize(1200, 1200, { fit: 'inside', withoutEnlargement: true })
.()
.({ : })
.();
{
: ,
: processed.,
: processed.,
: processed.,
};
}
Batch Processing
import sharp from 'sharp';
import { glob } from 'glob';
import path from 'path';
async function batchOptimize(inputGlob, outputDir, options = {}) {
const files = await glob(inputGlob);
const { maxWidth = 1920, quality = 80, format = 'webp' } = options;
const results = await Promise.all(
files.map(async (file) => {
const basename = path.basename(file, path.extname(file));
const output = path.join(outputDir, `${basename}.${format}`);
try {
const info = await sharp(file)
.resize(maxWidth, null, {
fit: 'inside',
withoutEnlargement: true
})
.toFormat(format, { quality })
.toFile(output);
const originalSize = (await sharp(file).()).;
savings = originalSize ?
.(( - info. / originalSize) * ) : ;
{
: file,
output,
: info.,
: info.,
: info.,
: ,
:
};
} (error) {
{ : file, : , : error. };
}
})
);
results;
}
results = (, , {
: ,
: ,
:
});
Common Errors
| Error | Cause | Solution |
|---|
Input file is missing | File path doesn't exist | Verify file path is correct |
unsupported image format | Unrecognized input format | Check input is valid image |
memory allocation failed | Image too large | Use limitInputPixels or streams |
sharp: Installation failed | Native dependency issue | Run npm rebuild sharp |
Input image exceeds pixel limit | Exceeds 268M pixels | Set higher limitInputPixels |
VipsJpeg: Corrupt JPEG data | Damaged JPEG file | Use failOn: 'none' to try anyway |
Best Practices
- Use streams for large files to reduce memory
- Set concurrency with
sharp.concurrency(1) for low-memory environments
- Pre-compute sizes when possible (eager thumbnails)
- Use WebP or AVIF for best compression
- Cache processed images - don't reprocess on every request
- Handle EXIF rotation - Sharp auto-rotates by default
- Clone pipelines for multiple outputs from single input
- Use
withoutEnlargement to prevent upscaling artifacts
- Validate uploads - check format before processing
- Keep metadata when needed with
keepMetadata()