image-size vs probe-image-size vs sharp metadata: Image Dimension Detection in Node.js (2026)
TL;DR
image-size is the lightweight image dimension detector — reads just enough bytes to determine width, height, and format, supports 15+ formats, no native dependencies. probe-image-size streams just the header bytes — works with URLs and streams, minimal data transfer, detects dimensions without downloading the full image. sharp's metadata uses libvips to read full image metadata — dimensions, color space, EXIF, ICC profiles, but requires native binaries. In 2026: image-size for local files (simple, fast), probe-image-size for remote URLs (minimal download), sharp metadata when you're already using sharp for image processing.
Key Takeaways
- image-size: ~15M weekly downloads — pure JS, sync/async, 15+ formats, zero native deps
- probe-image-size: ~3M weekly downloads — streams headers only, URL support, minimal bandwidth
- sharp metadata: ~10M weekly downloads — full metadata (EXIF, ICC), requires libvips native binary
- All three detect width, height, and format — different trade-offs
- image-size reads minimal bytes from buffers/files — fastest for local files
- probe-image-size is ideal for remote images — downloads only header bytes
image-size
image-size — lightweight dimension detection:
Basic usage
import sizeOf from "image-size"
// From a file path (sync):
const dimensions = sizeOf("./public/logo.png")
console.log(dimensions)
// → { width: 512, height: 512, type: "png" }
// From a buffer:
import { readFileSync } from "node:fs"
const buffer = readFileSync("./public/hero.jpg")
const size = sizeOf(buffer)
// → { width: 1920, height: 1080, type: "jpg" }
Async usage
import { imageSize } from "image-size"
import { readFile } from "node:fs/promises"
// Async with callback:
imageSize("./public/logo.png", (err, dimensions) => {
if (err) throw err
console.log(dimensions.width, dimensions.height)
})
// With promises:
const buffer = await readFile("./public/hero.jpg")
const dimensions = imageSize(buffer)
console.log(`${dimensions.width}x${dimensions.height}`)
// → "1920x1080"
Supported formats
import sizeOf from "image-size"
// Supports 15+ image formats:
sizeOf("photo.jpg") // → { width, height, type: "jpg" }
sizeOf("logo.png") // → { width, height, type: "png" }
sizeOf("icon.gif") // → { width, height, type: "gif" }
sizeOf("image.webp") // → { width, height, type: "webp" }
sizeOf("vector.svg") // → { width, height, type: "svg" }
sizeOf("photo.avif") // → { width, height, type: "avif" }
sizeOf("image.tiff") // → { width, height, type: "tiff" }
sizeOf("icon.ico") // → { width, height, type: "ico", images: [...] }
sizeOf("image.bmp") // → { width, height, type: "bmp" }
sizeOf("image.psd") // → { width, height, type: "psd" }
sizeOf("image.heif") // → { width, height, type: "heif" }
Multi-size images (ICO)
import sizeOf from "image-size"
// ICO files contain multiple sizes:
const ico = sizeOf("favicon.ico")
console.log(ico)
// → {
// width: 32, height: 32, type: "ico",
// images: [
// { width: 16, height: 16 },
// { width: 32, height: 32 },
// { width: 48, height: 48 },
// ]
// }
Use case: Image validation
import sizeOf from "image-size"
function validateImage(buffer: Buffer, options: {
maxWidth?: number
maxHeight?: number
allowedFormats?: string[]
}) {
const { width, height, type } = sizeOf(buffer)
if (options.maxWidth && width > options.maxWidth) {
throw new Error(`Image width ${width} exceeds max ${options.maxWidth}`)
}
if (options.maxHeight && height > options.maxHeight) {
throw new Error(`Image height ${height} exceeds max ${options.maxHeight}`)
}
if (options.allowedFormats && !options.allowedFormats.includes(type)) {
throw new Error(`Format "${type}" not allowed`)
}
return { width, height, type }
}
probe-image-size
probe-image-size — stream-based detection:
From URL (minimal download)
import probe from "probe-image-size"
// Probe a remote image — only downloads header bytes:
const result = await probe("https://pkgpulse.com/og/react-vs-vue.png")
console.log(result)
// → {
// width: 1200,
// height: 630,
// type: "png",
// mime: "image/png",
// wUnits: "px",
// hUnits: "px",
// }
// For a 5MB image, probe might download only ~100 bytes
From stream
import probe from "probe-image-size"
import { createReadStream } from "node:fs"
// From a file stream:
const stream = createReadStream("./public/hero.jpg")
const result = await probe(stream)
// → { width: 1920, height: 1080, type: "jpg", mime: "image/jpeg" }
// Stream is NOT fully consumed — only reads enough bytes for detection
From buffer
import probe from "probe-image-size"
// From a buffer:
const buffer = await readFile("./public/logo.png")
const result = probe.sync(buffer)
// → { width: 512, height: 512, type: "png", mime: "image/png" }
With HTTP options
import probe from "probe-image-size"
// Custom HTTP options for remote probing:
const result = await probe("https://private-cdn.com/image.jpg", {
headers: {
Authorization: "Bearer token",
"User-Agent": "PkgPulse/1.0",
},
timeout: 5000,
retries: 2,
follow_max: 3, // Max redirects
})
Use case: OG image validation
import probe from "probe-image-size"
async function validateOGImage(url: string) {
try {
const { width, height, type } = await probe(url)
const issues: string[] = []
// OG images should be 1200x630:
if (width < 1200) issues.push(`Width ${width} < 1200`)
if (height < 630) issues.push(`Height ${height} < 630`)
if (width / height > 2) issues.push("Aspect ratio too wide")
// Preferred formats:
if (!["png", "jpg", "webp"].includes(type)) {
issues.push(`Format "${type}" not recommended for OG images`)
}
return { valid: issues.length === 0, width, height, type, issues }
} catch (err) {
return { valid: false, issues: [`Failed to probe: ${err.message}`] }
}
}
sharp metadata
sharp — full image metadata:
Basic metadata
import sharp from "sharp"
// Read metadata from a file:
const metadata = await sharp("./public/hero.jpg").metadata()
console.log(metadata)
// → {
// width: 1920,
// height: 1080,
// format: "jpeg",
// space: "srgb",
// channels: 3,
// depth: "uchar",
// density: 72,
// chromaSubsampling: "4:2:0",
// isProgressive: false,
// hasProfile: true,
// hasAlpha: false,
// orientation: 1,
// exif: <Buffer ...>,
// icc: <Buffer ...>,
// }
From buffer
import sharp from "sharp"
const buffer = await readFile("./uploads/photo.jpg")
const { width, height, format, size } = await sharp(buffer).metadata()
console.log(`${width}x${height} ${format} (${size} bytes)`)
// → "1920x1080 jpeg (245760 bytes)"
EXIF data
import sharp from "sharp"
const metadata = await sharp("photo.jpg").metadata()
if (metadata.exif) {
// Parse EXIF with exif-reader:
const exifReader = await import("exif-reader")
const exif = exifReader.default(metadata.exif)
console.log(exif.Image?.Make) // "Apple"
console.log(exif.Image?.Model) // "iPhone 15 Pro"
console.log(exif.Photo?.DateTimeOriginal) // Date object
console.log(exif.GPSInfo?.GPSLatitude) // GPS coordinates
}
Stats (pixel analysis)
import sharp from "sharp"
// Get pixel statistics:
const stats = await sharp("image.png").stats()
console.log(stats)
// → {
// channels: [
// { min: 0, max: 255, sum: 12345678, mean: 128.5, ... }, // R
// { min: 0, max: 255, sum: 12345678, mean: 126.3, ... }, // G
// { min: 0, max: 255, sum: 12345678, mean: 130.1, ... }, // B
// ],
// isOpaque: true,
// dominant: { r: 45, g: 120, b: 200 },
// }
Feature Comparison
| Feature | image-size | probe-image-size | sharp metadata |
|---|---|---|---|
| Dimensions | ✅ | ✅ | ✅ |
| Format detection | ✅ | ✅ | ✅ |
| MIME type | ❌ | ✅ | ✅ |
| EXIF data | ❌ | ❌ | ✅ |
| ICC profiles | ❌ | ❌ | ✅ |
| Color space | ❌ | ❌ | ✅ |
| URL probing | ❌ | ✅ | ❌ |
| Stream support | ❌ | ✅ | ✅ |
| Sync API | ✅ | ✅ | ❌ |
| Native deps | ❌ (pure JS) | ❌ (pure JS) | ✅ (libvips) |
| Formats | 15+ | 10+ | 15+ |
| Size | ~20KB | ~15KB | ~50MB (with libvips) |
| Weekly downloads | ~15M | ~3M | ~10M |
When to Use Each
Use image-size if:
- Need quick width/height from local files or buffers
- Want zero native dependencies (pure JS)
- Building upload validation or image catalogs
- Need sync API for simple scripts
Use probe-image-size if:
- Need dimensions of remote images (URLs)
- Want to minimize bandwidth (only downloads header bytes)
- Building OG image validators or link previews
- Working with streams
Use sharp metadata if:
- Already using sharp for image processing
- Need EXIF data, ICC profiles, or color space info
- Need pixel statistics (dominant color, etc.)
- Building image processing pipelines
Methodology
Download data from npm registry (weekly average, February 2026). Feature comparison based on image-size v1.x, probe-image-size v7.x, and sharp v0.33.x.