Open-source project
PactInteractive/image-downloader avatar
PactInteractive/image-downloader

image-downloader: a browser extension for collecting images from a page

⬇️ Download images from the web with ease. A browser extension for Google Chrome, Microsoft Edge, and Brave.

993 stars327 forksJavaScriptNOASSERTION

At a glance

What is it?
Image Downloader is a Chrome, Edge and Brave extension for browsing and downloading the images on a web page, written with Preact and Tailwind and built with Bun. The repository is developer-facing rather than user-facing, so most of what it documents is the local build loop and the packaging, with the actual usage guide kept in a separate file.
Who is it for?
Adopt image-downloader when you work with pages that carry many images and want them gathered in one place rather than saved one right-click at a time, and do it from the store listing rather than from source unless you are changing the extension. It is the wrong tool for a bulk, automated harvesting job, since it is a browser extension driven by a person, and the wrong tool if you need to run it in a headless environment or on a schedule.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
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

A small extension with three browser targets

Image Downloader is a browser extension whose homepage points at a Chrome Web Store listing, and whose topics name three targets: a Google Chrome extension, a Microsoft Edge extension and a Brave extension. The description is short, download images from the web with ease, and the README's own tagline is browse and download images on the web.

That is a deliberately small scope. The extension does not attempt to be a download manager, and it does not try to handle video, audio or arbitrary files. It deals with images, and the browsing part of the name matters: the idea is that you open a page, see the images the extension has found, and take the ones you want rather than right-clicking each one.

The repository layout is small and mostly self-explanatory. There is a manifest.json at the root, which is the extension manifest that the browser reads, a src directory with the source, a scripts directory holding the build and watch scripts, a tsconfig.json, a bun.lock file for dependency locking, images for the logo, and a USERGUIDE.md that the README points to for anyone who wants to know how to use the extension.

The separation between those two documents is the first structural thing to notice. The README says explicitly that if you are here to learn how to use the extension, go to the User Guide, and if you are a developer interested in running the extension locally rather than installing it from the Chrome Web Store, keep reading. Everything that follows in the README is for the second audience. The usage documentation is a separate file, which is a reasonable choice for a project with a public listing and a public source repository, since the two audiences do not overlap much.

There is also an AGENTS.md at the root, which is a convention that has become common in repositories where an AI coding assistant is expected to work on the codebase, and a .prettierrc and .prettierignore for formatting, with a matching lint script that runs prettier over the tree.

The Bun loop: install, watch, load unpacked

The local development instructions are the most concrete part of the README, and they run on Bun rather than npm. The dependency install is:

bash
bun install

After that there are two ways to build. The development server watches for file changes automatically:

bash
bun start

Or, to build only once:

bash
bun run build

The package scripts explain what those commands do. The start script is bun run build followed by bun scripts/watch.ts, so the watcher always builds first, which is the right order for a project where the build step is what produces the loadable extension. The build script runs bun scripts/build.ts and then the zip step.

The result of a build is a folder that the browser can load. The instructions are to open the extension list at chrome://extensions, enable Developer mode, click Load unpacked, go to the extension root folder and pick the build folder. That is the standard unpacked-extension workflow, and the detail that matters is that the loadable artefact is the build directory, not the source directory.

Because the same extension is targeted at Chrome, Edge and Brave, all of which are Chromium browsers, one unpacked build covers all three, and the Chrome extensions page is the place to load it in each case.

The build scripts are TypeScript files run by Bun, which is also why the toolchain is Bun rather than a bundler configuration in the manifest. There is no webpack or vite configuration to reason about; the build logic lives in scripts/build.ts, and the watcher in scripts/watch.ts, so anyone changing the packaging behaviour is editing TypeScript rather than adjusting a config file.

Tests run on the Bun test runner with a DOM shim

Testing an extension has one awkward aspect: a large part of the code runs against a browser DOM, so the tests need a DOM. The dependency list resolves that with happy-dom at 21.0.3, a DOM implementation for Node, paired with the Bun test runner.

The two documented commands are the same shape as the build ones. During development you watch the tests and only the ones related to locally changed files are run:

bash
bun test --watch

And the plain form runs everything once:

bash
bun test

The package script for the non-watching form is simply bun test, and the test script is listed in the scripts section next to the build and the dev tasks, so the standard npm-style invocation works too if the environment has node compatibility for it.

The other testing-adjacent dependency is @types/chrome, which supplies the types for the extension APIs, and htm, which is a tagged template library for building elements without a compiler step. The presence of htm alongside Preact is worth a note, because it is the alternative to JSX: the components are written with template literals rather than with a build-time transform, which keeps the build step small. The TypeScript configuration at the root is what the type checking script uses, and the types script runs bun check.

For a project of this size, that is a coherent and current stack: Bun for the toolchain and the test runner, Preact for the interface, happy-dom for the test environment, and TypeScript for the types without a bundler pipeline.

Preact, signals and the image processing dependencies

The dependency list is where the design of the extension becomes visible, and it is worth reading as a description of the feature set.

The user interface is Preact at 11.0.1, with @preact/signals and @preact/signals-core for reactive state. Signals are a good fit for this kind of interface, where a list of images is filtered, selected and counted, and where the counts in the toolbar and the list itself have to stay in step without a full re-render of the panel. The styling is Tailwind CSS 4.3.3 with the Tailwind CLI and a prettier plugin for Tailwind, so class names are sorted by the formatter, which is what keeps a Preact plus Tailwind codebase consistent as more people contribute.

Two dependencies point at features rather than plumbing. sharp at 0.35.5 is a high performance image processing library, native in turn, which is what you would reach for to generate thumbnails or to inspect the dimensions of an image before listing it. nouislider at 15.8.1 is a slider control, and a slider in an image browser is almost always a size control for the grid or zoom, so this points at a gallery view with adjustable thumbnails.

On the plumbing side, glob and glob-watcher with fs-extra are what the build and watch scripts use to find and copy files, and @types/glob-watcher and @types/fs-extra are there for the TypeScript side of that. sneer is listed as well, and it is the emoji picker component, which is a small sign of how a project like this grows extra affordances over time.

The observable part of the extension, then, is a Preact panel over a Chromium page, with a filtered grid of images, reactive counts, a size slider and image processing behind the scenes. The README does not describe any of that in user terms, which is why the User Guide file exists.

Packaging: the Chrome build, the Firefox build and the zip artefacts

The build produces more than one artefact, and the packaging script makes the shape of the release visible:

bash
rm -f ../image-downloader.zip ../image-downloader-firefox.zip && (cd build && zip -qr ../../image-downloader.zip .) && (cd build-firefox && zip -qr ../../image-downloader-firefox.zip .)

So the build step writes a build directory and a build-firefox directory, and the zip step packs each into a distributable archive. The Firefox output exists even though the topics name Chrome, Edge and Brave, and the interesting question is why. The likely reason is the difference in extension APIs between the two browsers, since Firefox does not implement the Chrome extension APIs in full and needs its own manifest and its own background script handling, and the separate build directory is how a project absorbs that difference without branching the whole source tree.

The script also does a clean of the two archives before packing them, so a stale zip cannot be shipped by accident, and the pack itself runs from inside the build directory with a quiet recursive zip.

The extension manifest at the repository root is the file the browser reads to learn the extension's name, its permissions and its entry points, and it is the place where the Firefox divergence would show up first. The README does not describe the manifest's contents, and it does not explain the Chrome and Firefox differences, so a contributor touching the background script has to look at both build targets.

The other artefact worth noting is the Chrome Web Store listing, which the repository treats as the primary distribution channel. The homepage of the project is that listing, and the README only offers the local build as an alternative for developers.

Version drift, the licence and the scripts nobody expects

Three details in the metadata are worth calling out, because each of them describes something a prospective user or contributor will hit.

The first is version drift. The package manifest records version 4.6.0, while the most recent release tag in the repository is v4.4.0 from 2026-04-19, with v4.3.0 and v4.2.1 a day earlier. There are no GitHub releases attached beyond those tags, and the last push was on 2026-10-17, so the source tree is well ahead of the newest tag. The manifest is marked private, which is normal for an application rather than a library, but it means the version in it tracks the development build and not the published extension. The store listing is what a user actually gets, and its version is not visible from this repository.

The second is the licence. The manifest records GPLv3, and the repository's own licence field is classified by GitHub as a custom licence it cannot categorise, with the terms in a LICENSE.md file. Those two descriptions are close enough to be the same thing and different enough to be confusing, and since the LICENSE.md file is the document that governs, that is the one to read. GPL-3.0 on a browser extension is an unusual choice compared with the permissive licences most extensions use, and it is worth understanding what it obliges you to do if you fork the extension and distribute it.

The third is a set of scripts that have nothing to do with the extension. The package scripts include a command that runs code-server on port 4095 alongside a tailscale command that resets and serves two ports in the background, one for the web server and one for code-server. That is a remote development setup, exposing a development environment over a tailnet so the extension can be worked on from another machine. It is a reasonable internal workflow, and it is also a reminder that the scripts in this manifest are not only for building the extension.

The remaining scripts are ordinary. The lint script runs prettier over the tree, the types script runs bun check, the task script runs scripts/task.ts, and the web script runs a watched scripts/web.ts, which is presumably a development harness for the interface.

Editorial conclusion

Adopt image-downloader when you work with pages that carry many images and want them gathered in one place rather than saved one right-click at a time, and do it from the store listing rather than from source unless you are changing the extension. It is the wrong tool for a bulk, automated harvesting job, since it is a browser extension driven by a person, and the wrong tool if you need to run it in a headless environment or on a schedule. Before installing, read the USERGUIDE.md file in the repository, because the README itself is aimed at developers running the extension locally and says nothing about how to use it once installed.

Frequently asked questions

What is the Image Downloader browser extension?

It is an extension for browsing and downloading images on the web, published in the Chrome Web Store and targeting Chrome, Microsoft Edge and Brave, all of which are Chromium browsers. The repository is a JavaScript and TypeScript project built with Bun and Preact.

How do I run the Image Downloader extension locally for development?

Run bun install, then bun start for a watching development server or bun run build for a one-off build, open chrome://extensions, enable Developer mode, click Load unpacked and pick the build folder in the extension root.

How are the tests run in this project?

With the Bun test runner, using happy-dom to provide the DOM the extension code needs. Run bun test --watch to run and watch the tests related to locally changed files, or bun test to run all of them once.

What artefacts does the build produce?

A build directory for Chromium browsers and a build-firefox directory, which the build:zip script packs into image-downloader.zip and image-downloader-firefox.zip respectively, removing the old archives first so a stale package cannot be shipped.

Where is the user documentation for the extension?

In the USERGUIDE.md file at the root of the repository, which the README links to. The README itself is aimed at developers running the extension locally rather than at people using it once installed from the Chrome Web Store.

Official sources

  1. Issues
  2. PactInteractive/image-downloader on GitHub
  3. Project website
  4. README
  5. Releases
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/pactinteractive-image-downloader.svg)](https://hysenlabs.com/projects/pactinteractive-image-downloader)

Was this useful?