Library / SDK
nodeca/probe-image-size avatar
nodeca/probe-image-size

probe-image-size: read an image's dimensions without downloading the image

Get image size without full download. Supported image types: JPG, GIF, PNG, WebP, BMP, TIFF, SVG, PSD, ICO.

1,032 stars72 forksJavaScriptMIT

At a glance

What is it?
probe-image-size is a small JavaScript library that determines the width and height of an image from a URL, a stream, or a buffer, reading only the bytes it needs. It supports JPG, GIF, PNG, WebP, BMP, TIFF, SVG, PSD, ICO, AVIF, HEIC, and HEIF, and its HTTP path is built on needle with range requests.
Who is it for?
probe-image-size fits a service that has to lay out a grid of images it does not host, a thumbnail pipeline that wants dimensions before committing to a download, and a browser build that needs a size without a full fetch. It is the wrong choice for a synchronous server-side pipeline, since the documentation explicitly recommends the async version as memory-friendly, and it is the wrong choice when you need the pixels rather than the numbers.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem is the wasted download, not the parsing

probe-image-size gets the size of an image without a full download. The supported types are JPG, GIF, PNG, WebP, BMP, TIFF, SVG, PSD, ICO, AVIF, HEIC, and HEIF. The npm package description names the shorter list JPG, GIF, PNG, WebP, BMP, TIFF, PSD, which is a reasonable summary of the common cases.

The key features are listed as small size with no heavy dependencies, works with remote and local data, effective with big images in terms of speed and memory by downloading minimal data from remotes, extracts the orientation value when available, and is easy to browserify because it is split into components.

The third of those is the reason the library exists. Knowing that an image is 4000 by 3000 pixels usually requires reading a header, and the header is at the front of the file. Downloading 40 megabytes to read 20 bytes that say the dimensions is the wasteful case, and for a service that lays out a gallery of remote images that is the normal case, not the exception.

The orientation extraction is the other feature that distinguishes it from a plain header reader. Camera images record an orientation flag in their Exif data, and a naive size probe that ignores it will report the sensor dimensions rather than the dimensions of the image as displayed. A library that returns the orientation alongside the width and height has done the harder part of that problem, and the documentation is careful about what it returns and what you have to do with it.

The dependency list confirms the smallness claim. There are three runtime dependencies, `lodash.merge` for merging options, `needle` as the HTTP client, and `stream-parser` for reading streams.

One function, three input shapes, and a synchronous variant

The API is small and the example in the project summary shows all of it:

js
const probe = require('probe-image-size');

// Get by URL
let result = await probe('http://example.com/image.jpg');
console.log(result); // =>
/*
  {
    width: xx,
    height: yy,
    type: 'jpg',
    mime: 'image/jpeg',
    wUnits: 'px',
    hUnits: 'px',
    url: 'http://example.com/image.jpg'
  }
*/


// By URL with options
let result = await probe('http://example.com/image.jpg', { rejectUnauthorized: false });
console.log(result);


// From the stream
let result = await probe(require('fs').createReadStream('image.jpg'));
console.log(result);


// From a Buffer (sync)
let data = require('fs').readFileSync('image.jpg');
console.log(probe.sync(data));

The signature is `probe(src [, options|keepOpen])` returning a Promise, where `src` is either a String URL to fetch or a readable Stream. The `options` argument is HTTP only and points at the needle documentation for request options, with customised defaults in the library's own http.js. The `keepOpen` flag is stream only, and keeps the input stream open after the parser finishes, since the input stream is closed by default.

The synchronous variant, `probe.sync(src)`, returns the result or null on failure, and can eat arrays, typed arrays, and buffers.

The three entry points are the same parser behind different I/O. The library is split accordingly, and the API documentation notes that you can access or browserify `stream.js`, `http.js`, and `sync.js` directly, and that if you do not like the dependencies in `http.js` you can create your own wrapper for `stream.js`. That last point is the escape hatch for a browser bundle where pulling in an HTTP client would be unwelcome.

What the result object actually tells you

The result object is documented field by field, and the comments carry more meaning than the field names do.

`width` and `height` are the image size before any transformations, meaning orientation and cropping, are applied. The orientation is returned separately, as a number from 1 to 8, and you may wish to swap width and height if the orientation is 5 or more. The documentation explains why it is separate rather than applied: you may wish to apply it afterwards depending on browser support, and browsers only support JPEG orientation for now. The `known_issues.md` file in the repository is pointed to for details.

`length` is the byte length of the file, if available, which is HTTP only. `type` is the image type, described as the usual file name extension, and `mime` is the mime type. `wUnits` and `hUnits` are the width and height units types, px by default, and they can be different for SVG, which is the one format here that is not measured in pixels.

`url` is HTTP only, and it is the last url for the image in the chain of redirects, or the same as the source if there were no redirects. That field is easy to overlook and it matters, because a redirect means the URL you recorded is not the URL the image came from.

`variants` is optional and holds the full list of sizes, always for ICO and for AVIF if there are multiple images. An ICO file can contain several images at different sizes, and an AVIF can be a multi-image file, so this is the field that tells you a single width and height are not the whole story.

Two error extensions are documented. `code` equals `ECONTENT` if the library failed to parse the file, and `status` equals an HTTP status code if it receives a non-200 response. So a failure tells you whether the file was unparseable or the request failed, which are different problems with different fixes.

The warning about untrusted sources is the most important line

There is a warning block in the API section, and it is worth reading as the headline of the whole library rather than as a footnote.

For untrusted sources, the returned `width` and `height` should not be treated as trusted values. You may need to additionally check that they are within ranges acceptable for your application.

The reasoning is straightforward once you see it. A size probe is a parser that takes attacker-controlled bytes and produces numbers that your application then uses, often to allocate a buffer, size a canvas, or set a layout dimension. If the parser is handed a malformed file, or a file crafted to produce a value your code does not expect, the number you get is not a bounded value. A file that claims to be 65535 by 65535 when it is actually a small JPEG is not a crash, but a file that produces a negative or absurd number can be.

That is a security consideration that applies to every image header parser, and it is unusual to see it stated this plainly in a library's own documentation. It is the same class of warning you would want on a file upload validator, and it exists because the library is small and generic enough that people will point it at anything.

The second warning, in the sync section, is about a different failure mode. Formats like JPEG and TIFF can store the size anywhere, far from the head. That usually does not happen, but if you need guarantees, always provide full file content to the sync methods, and the project strongly recommends using the async version as memory-friendly.

The two warnings together describe the library's shape. The async path reads only the bytes it needs and closes the stream. The sync path has the whole file in memory already, which is what makes it fast and simple, and also what makes it the wrong choice for a large file.

The `keepOpen` option exists for the case where you need the stream afterwards, since the input stream is closed by default once the parser finishes.

Three files at the root, and a small dependency surface

The repository layout is the API, literally. At the root there is `index.js`, `http.js`, `stream.js`, and `sync.js`, plus a `lib/` directory, and those are exactly the four entries in the `files` array of package.json, along with `lib/`. So the published package contains the three I/O wrappers and the parsers, and nothing else.

That split is why the browserify hint is there. The `stream.js` entry has no HTTP dependency, so a browser bundle that only needs to probe a file the user picked can include just the stream parser. If you want the URL form in a browser, you pull in `http.js` and with it needle and its dependencies, which is why the documentation says you can create your own wrapper for `stream.js` if you do not like the http.js dependencies.

The parsers live in `lib/`, and the coverage configuration in package.json targets the lib directory, `index.js`, `http.js`, `stream.js`, and `sync.js` explicitly, which tells you the library of interest is the parser collection in `lib/` and that the three wrappers are thin.

The test setup is modern and minimal. The test script is `npm run lint && node --test`, using the built-in node test runner rather than a framework, with eslint 9.39.4 and the `neostandard` preset for style. The coverage script uses c8 with text, html, and lcov reporters. There is an `eslint.config.js` at the root, a `known_issues.md` document, a `CHANGELOG.md`, and a `support/` directory.

The `known_issues.md` file deserves attention from anyone evaluating the library, because the orientation behaviour in particular has browser-specific caveats and the documentation points there for details rather than pretending the behaviour is uniform.

The licence is MIT, and the funding section lists two GitHub Sponsors entries, the individual author and the organisation, plus a Tidelift subscription route.

Where probe-image-size is the wrong tool

The boundaries are clear, and they are about what you actually need.

First, if you need the pixels, this is the wrong library. It returns dimensions and metadata, nothing more. A thumbnailer still has to download and decode the image, and this library saves you the wasted bandwidth of the download only if the decision you make on the dimensions can be made without looking at the content. For a gallery layout that is exactly the case. For anything that has to look at the image, it is an extra round trip rather than a saving.

Second, the synchronous variant is a trap for large files. The documentation is explicit: formats like JPEG and TIFF can store the size anywhere, far from the head, so the sync method needs the whole file, and the async version is strongly recommended as memory-friendly. Using `probe.sync` on a 50 megabyte TIFF means holding 50 megabytes in memory to read two numbers, when the async path would have read a few kilobytes.

Third, the format list is not everything. The supported types are named, and if you are handed something else, a .avif variant with an unusual box layout, or a format that stores its dimensions in a proprietary chunk, the parser returns an `ECONTENT` error. There is no generic fallback.

Fourth, the HTTP path is a real network request with a real failure surface. The `status` field tells you about non-200 responses, and the `url` field tells you about redirects, but a slow server, a partial response, or a server that ignores range requests is a case you will have to handle yourself, and the `rejectUnauthorized: false` option in the example is a reminder that TLS verification is a decision the caller makes.

Fifth, and this is the one that bites in a browser, the orientation handling is deliberately not applied. Width and height are the pre-transformation values, and you are expected to swap them yourself when the orientation is 5 or above. A consumer that forgets produces images that are rotated ninety degrees, and no error is raised, because from the library's point of view nothing is wrong. The `known_issues.md` document is where the details of that live, and a browser-facing application should read it before shipping.

Editorial conclusion

probe-image-size fits a service that has to lay out a grid of images it does not host, a thumbnail pipeline that wants dimensions before committing to a download, and a browser build that needs a size without a full fetch. It is the wrong choice for a synchronous server-side pipeline, since the documentation explicitly recommends the async version as memory-friendly, and it is the wrong choice when you need the pixels rather than the numbers. Check three things before you rely on it: that your image formats are on the supported list, that you read the warning about untrusted sources, since returned width and height are not to be treated as trusted values, and that you understand that the reported width and height are the pre-transformation size, with orientation returned separately and needing to be applied yourself.

Frequently asked questions

How do I install and use probe-image-size?

Install it with `npm install probe-image-size`, then call `probe(src)` with a URL string or a readable stream and await the result, or call `probe.sync(buffer)` for a buffer, a typed array, or an array. The result gives width, height, type, mime, and unit fields, with orientation returned separately when it is available.

Which image formats does probe-image-size support?

The README lists JPG, GIF, PNG, WebP, BMP, TIFF, SVG, PSD, ICO, AVIF, HEIC, and HEIF. The npm package description covers the shorter list of JPG, GIF, PNG, WebP, BMP, TIFF, and PSD.

What does probe-image-size return for orientation, and do I have to apply it?

The returned width and height represent the image size before any transformations are applied, and the orientation is returned separately as a number from 1 to 8, so you may wish to swap width and height if the orientation is 5 or greater. The project points to known_issues.md for details, since browsers only support JPEG orientation for now.

Is it safe to use probe-image-size on untrusted images?

The project warns that for untrusted sources the returned width and height should not be treated as trusted values, and that you may need to additionally check that they are within ranges acceptable for your application. It also notes that JPEG and TIFF can store the size far from the head, so the sync method should always be given full file content, and the async version is recommended as memory-friendly.

Official sources

  1. Issues
  2. License: MIT
  3. nodeca/probe-image-size on GitHub
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/nodeca-probe-image-size.svg)](https://hysenlabs.com/projects/nodeca-probe-image-size)