Skip to main content

image-size vs probe-image-size vs sharp metadata: Image Dimension Detection in Node.js (2026)

·PkgPulse Team

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

Featureimage-sizeprobe-image-sizesharp 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)
Formats15+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.

Compare image processing and media tooling on PkgPulse →

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.