# Bulk Image Downloader & Converter: WebP, Resize, ZIP (`tamatama/bulk-image-downloader`) Actor

Bulk image downloader from $0.90 per 1,000 images ($1.50 on the Free plan, less on paid plans), no fee by file size. Download and convert in one run: WebP, JPEG or PNG, max width and height, quality. From URLs, an Apify dataset, CSV, Excel or Google Sheet; files named by your column, plus a ZIP.

- **URL**: https://apify.com/tamatama/bulk-image-downloader.md
- **Developed by:** [Otama Labs](https://apify.com/tamatama) (community)
- **Categories:** Automation, Developer tools, E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.90 / 1,000 images

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Bulk Image Downloader & Converter: WebP, Resize, ZIP

**Bulk image downloader for online shops, marketers and data teams who need a pile of image links turned into ready-to-use files.** Give it image links, the dataset of a scraper run, or a CSV, Excel file or Google Sheet with an image column, and one run downloads every image, **converts it to WebP, JPEG or PNG**, **shrinks it to a max width or height**, **names it after your own column** (SKU, product ID), stores each file with its own link and packs them all into **one ZIP**.

**From $0.90 per 1,000 images** ($1.50 on the Free plan, less on paid plans), **with no extra fee by file size**. Failed downloads, duplicates and the ZIP are free.

### What you can use it for

- **Shop catalogs**: a supplier sends a sheet of product image links; you get light WebP files named by SKU, ready to upload.
- **After a scraper**: point it at a product or listing scraper's dataset, or add it to the scraper as an integration so that every run's pictures are saved automatically, as files, not as links that may expire.
- **Faster pages**: batch-shrink large photos to 1,200 px WebP. In the example below a 494 KB, 1,920 px JPEG became a 90 KB, 800 px WebP.
- **Image datasets for AI**: one format and size for every picture, plus a SHA-256 hash per file to spot exact copies.

### Output example

From a run of the prefilled input (three public-domain images, **Convert to = WebP**, **Max width = 800**). Each image is one dataset row:

```json
{
  "status": "ok",
  "sourceUrl": "https://images-assets.nasa.gov/image/GSFC_20171208_Archive_e001788/GSFC_20171208_Archive_e001788~large.jpg",
  "fileName": "GSFC_20171208_Archive_e001788-large.webp",
  "format": "webp",
  "bytesIn": 493767,
  "bytesOut": 89746,
  "width": 1920,
  "height": 1920,
  "outWidth": 800,
  "outHeight": 800,
  "sha256": "d1618042e09a887f8ed5a3ece266f1aceb0df4b785d7fa3a8a41638f2e1fa7a1",
  "fileUrl": "https://api.apify.com/v2/key-value-stores/7aB8hAPPODOaXAkRm/records/GSFC_20171208_Archive_e001788-large.webp?signature=YiJPbQtPae7r21hgEPVG",
  "error": null,
  "processedAt": "2026-10-28T04:33:55.300577+00:00"
}
```

The **OUTPUT** record of the run holds the ZIP link (`zipUrls`) and the totals; the status message shows the ZIP link as well. With a dataset or sheet as input, each row also carries `rowNumber` and `sourceRow` (that row's own columns), so the results join back to your data.

Credit for the prefilled images: NASA Goddard (archive photo), NASA/JPL (Voyager 1, "Pale Blue Dot", via Wikimedia Commons) and the Wikimedia Commons public-domain example file. They are used only as a demo.

### How to use

1. Give the images in any of these ways (they can be combined):
   - **Image URLs**: direct links to image files, or a link to a text file that lists them.
   - **Bulk**: pick a dataset from an earlier Apify run, or paste a link to your product sheet (CSV, Excel, JSON, or a Google Sheet anyone with the link can view). The Actor guesses which column holds the pictures; name it in **URL column** if it guesses wrong (`images.0` = first link of a list field).
2. Pick **Convert to** (WebP, JPEG, PNG, or Original to keep each file as downloaded) and, if you like, **Max width** / **Max height** and **Quality**.
3. Set **File name column** (e.g. `sku`) to name the files after your data.
4. Press **Start**. Download the ZIP, or export the dataset as CSV or Excel to get your sheet back with a link to every file.

### Input

| Field | What it does |
|---|---|
| **Image URLs** | Direct image links (JPEG, PNG, WebP, GIF, BMP, TIFF, AVIF), e.g. `https://example.com/img/shoe-123.jpg`. Web pages are not crawled. |
| Convert to | `webp` (default), `jpeg`, `png` or `original` (bytes unchanged, no resizing; also for SVG and HEIC). |
| Max width / Max height | Larger images are shrunk to fit, keeping the aspect ratio; smaller ones are never enlarged. 0 = no limit. |
| Quality | 1-100 for WebP and JPEG (default 80). |
| ZIP file | `single` (default): one ZIP with every image, in 250 MB parts for very large runs. `none`: files only. |
| Apify dataset with image links | A dataset ID, e.g. of a scraper run. Up to 10,000 items. |
| Spreadsheet URL | Link to your sheet: CSV, Excel (first sheet of an .xlsx), JSON / JSON Lines, or Google Sheets. At most 50 MB and 10,000 rows; a bare column of links with no header is fine. |
| URL column | The column with the image links (dots for nested fields and list items, e.g. `product.image` or `images.0`). Empty = the column that most looks like image links. |
| File name column | Name the files after this column (e.g. `sku`). Empty = the name in the link. Names are made safe for storage and unique (`-2`, `-3` are added). |
| Max images | Default 1,000, up to 10,000 per run. |
| Max file size (MB) | Default 50, up to 200. Larger downloads are skipped and not charged. |

### How it works

1. Links from all inputs are collected; each link is downloaded once (later rows with the same link get a free `duplicate` row pointing to the same file).
2. At most 2 downloads run at a time per host, at least 0.5 s apart. HTTP 429, 5xx and network errors are retried twice in the same run (after 5 s and 15 s). Links, URL lists and sheets that point to local or private network addresses are refused, also after redirects.
3. Each download is checked to be an image, turned upright when a phone saved it sideways (EXIF), resized and encoded; color profiles are kept. JPEG output puts transparent areas on white; WebP and PNG keep transparency. Animated GIFs keep their first frame when converted.
4. Each file is stored in the run's key-value store under its file name and added to the ZIP. If Apify moves the run to another server, images already saved are not downloaded or charged again.

### Pricing

Pay per event:

| Event | Free plan | Bronze | Silver | Gold and above |
|---|---|---|---|---|
| `image-processed`: an image downloaded, converted and saved | $0.0015 | $0.0013 | $0.0011 | $0.0009 |

Plus Apify's small start fee of $0.00005 per GB of memory per run (the default 1 GB counts once). There is **no extra charge by file size and none for the ZIP**, and platform usage is included.

On the Free plan, 1,000 product photos cost $1.50 and 10,000 cost $15. Links that fail, are not images, are too large or repeat an earlier link are reported and **not charged**. With a spending limit on the run, the Actor stops starting new images once the limit is reached.

### Limits

- Only direct image links are downloaded: no crawling of web pages for images, no logins, no paywalls. Signed links must still be valid when the run starts.
- Up to 200 MB per file (50 MB by default) and 100 megapixels per image. SVG and HEIC can only be kept as they are (`original`).
- Speed: roughly 1-3 seconds per large photo with the default 1 GB of memory, plus the 2-per-host download limit. Give big runs more memory (4 GB = one full CPU core) and a longer timeout; the Actor stops starting new images about 3 minutes before the timeout so that the ZIP and the rows are saved.
- Please download only images you are allowed to use and store.

### FAQ

**Can it download the images of a scraper run?** Yes: paste the run's dataset ID into **Apify dataset with image links**. When the pictures sit in a list field, set **URL column** to `images.0` (any index works).

**How do I save the pictures of every scraper run automatically?**

1. On the scraper's page in Apify Console, go to **Integrations**, add an integration, find **Bulk Image Downloader & Converter**, and set it to start when the scraper's run succeeds.
2. In the integration's input for this Actor, put `{{resource.defaultDatasetId}}` into **Apify dataset with image links** (`datasetId`). Apify swaps the variable for the ID of the run that just finished.
3. Say where the picture is: **URL column** (`urlField`), e.g. `imageUrl`, or `images.0` for the first photo of a list. Add **File name column** (`fileNameField`), e.g. `sku`, to name the files after your products.
4. Choose the format and size as usual. If the dataset box does not accept the variable, switch the input to JSON, e.g. `{"datasetId": "{{resource.defaultDatasetId}}", "urlField": "images.0", "fileNameField": "sku", "format": "webp", "maxWidth": 1200}`.

Checked on 30 October 2026 with an integration created through the Apify API that started this Actor after an OCR run (`"urlField": "url"`): the image from that run's dataset came out as a WebP file. The Actor can read only the datasets you pick in its input, not the one mentioned in the integration's event data, so an integration input without `datasetId` ends with a message naming the field to fill (the form's example images are not downloaded or charged). A scraper run that found nothing ends normally with "The dataset is empty".

**How do I get my sheet back with the new links?** Export the run's dataset as CSV or Excel; every row has `fileUrl` next to your own columns in `sourceRow`.

**WebP, JPEG or PNG?** WebP photos are usually far smaller than JPEG at the same look, and current browsers show them. JPEG suits older tools; PNG suits screenshots and logos with sharp edges.

**Can I use it from my own code or a workflow tool?** Send `{"urls": [...], "format": "webp", "maxWidth": 1200}` to the Apify API and read the dataset, or use `run-sync-get-dataset-items` for small batches. The Apify modules for Make, n8n and Zapier and the Apify loader for LangChain can start the Actor and pick up its rows.

### Related Actors by the same author

| Actor | What it does |
|---|---|
| [Image to Text & PDF OCR](https://apify.com/tamatama/image-pdf-ocr) | Read the text in images and scanned PDFs, also in bulk from a sheet or dataset. |
| [PDF to Markdown](https://apify.com/tamatama/doc-to-markdown) | PDF, Word, Excel and PowerPoint files to Markdown and RAG chunks. |
| [Data Cleaner](https://apify.com/tamatama/data-normalizer) | Clean phone numbers, URLs, prices, dates and company names in a dataset or CSV. |

### For AI Agents

An agent that has a list of image links can save them as files in a chosen format and size in one call. **Add this Actor alone through the Apify MCP server**:

```json
{
  "mcpServers": {
    "bulk-image-downloader": {
      "url": "https://mcp.apify.com?tools=tamatama/bulk-image-downloader"
    }
  }
}
```

The client signs in with OAuth (or sends `Authorization: Bearer <APIFY_TOKEN>`), and the Actor appears as the tool `tamatama--bulk-image-downloader`. A typical call:

```json
{ "urls": [{ "url": "https://example.com/photo.jpg" }], "format": "webp", "maxWidth": 1200, "zip": "none" }
```

Read `status`, `fileUrl` and `error` in each row; `error` rows cost nothing. Tool importers can use the OpenAPI file at `https://api.apify.com/v2/acts/tamatama~bulk-image-downloader/builds/default/openapi.json`.

# Actor input Schema

## `urls` (type: `array`):

Direct links to image files, e.g. https://example.com/img/shoe-123.jpg (JPEG, PNG, WebP, GIF, BMP, TIFF, AVIF; SVG and HEIC only with Convert to = Original). You can also link a text file that lists image URLs. For a spreadsheet or a dataset of links, use the bulk fields below (and remove these example links). Web pages are not crawled: give the image links themselves.

## `format` (type: `string`):

WebP is the smallest for photos on the web. Original keeps the downloaded bytes unchanged (no resizing).

## `maxWidth` (type: `integer`):

Shrink wider images to this width, keeping the aspect ratio. Smaller images are never enlarged. 0 = keep the size.

## `maxHeight` (type: `integer`):

Shrink taller images to this height, keeping the aspect ratio. 0 = no limit.

## `quality` (type: `integer`):

1-100. 80 is a good balance of size and quality for photos.

## `zip` (type: `string`):

Also pack all images into one ZIP file (split into 250 MB parts for very large runs). The link is in the run's OUTPUT record and status message. Each image is saved on its own in any case.

## `datasetId` (type: `string`):

Download the images linked in an Apify dataset, e.g. the output of a product scraper run. One image per item, up to 10,000 items; the item's own fields come back in sourceRow. In an integration that starts this Actor after another Actor's run, set it to {{resource.defaultDatasetId}}.

## `fileUrl` (type: `string`):

A public link to a CSV, Excel (.xlsx, first sheet), JSON or JSON Lines file with one image link per row, up to 50 MB and 10,000 rows, e.g. https://example.com/products.csv. A plain list of links without a header row works too. A Google Sheets link works when the sheet is shared as "Anyone with the link" (the first tab, or the tab in the link).

## `urlField` (type: `string`):

The column (or dataset field) that holds the image link, e.g. imageUrl or images.0. Leave empty to use the column that most looks like image links.

## `fileNameField` (type: `string`):

Name each file after this column, e.g. sku or productId (letters, digits and -\_.'() are kept; the extension is added). Leave empty to use the name in the link.

## `maxFiles` (type: `integer`):

Stop after this many images (duplicates are not counted).

## `maxFileMb` (type: `integer`):

Larger downloads are skipped and reported (not charged).

## Actor input object example

```json
{
  "urls": [
    {
      "url": "https://images-assets.nasa.gov/image/GSFC_20171208_Archive_e001788/GSFC_20171208_Archive_e001788~large.jpg"
    },
    {
      "url": "https://upload.wikimedia.org/wikipedia/commons/7/73/Pale_Blue_Dot.png"
    },
    {
      "url": "https://upload.wikimedia.org/wikipedia/commons/a/a9/Example.jpg"
    }
  ],
  "format": "webp",
  "maxWidth": 800,
  "maxHeight": 0,
  "quality": 80,
  "zip": "single",
  "maxFiles": 1000,
  "maxFileMb": 50
}
```

# Actor output Schema

## `images` (type: `string`):

No description

## `zip` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        {
            "url": "https://images-assets.nasa.gov/image/GSFC_20171208_Archive_e001788/GSFC_20171208_Archive_e001788~large.jpg"
        },
        {
            "url": "https://upload.wikimedia.org/wikipedia/commons/7/73/Pale_Blue_Dot.png"
        },
        {
            "url": "https://upload.wikimedia.org/wikipedia/commons/a/a9/Example.jpg"
        }
    ],
    "format": "webp",
    "maxWidth": 800
};

// Run the Actor and wait for it to finish
const run = await client.actor("tamatama/bulk-image-downloader").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "urls": [
        { "url": "https://images-assets.nasa.gov/image/GSFC_20171208_Archive_e001788/GSFC_20171208_Archive_e001788~large.jpg" },
        { "url": "https://upload.wikimedia.org/wikipedia/commons/7/73/Pale_Blue_Dot.png" },
        { "url": "https://upload.wikimedia.org/wikipedia/commons/a/a9/Example.jpg" },
    ],
    "format": "webp",
    "maxWidth": 800,
}

# Run the Actor and wait for it to finish
run = client.actor("tamatama/bulk-image-downloader").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    {
      "url": "https://images-assets.nasa.gov/image/GSFC_20171208_Archive_e001788/GSFC_20171208_Archive_e001788~large.jpg"
    },
    {
      "url": "https://upload.wikimedia.org/wikipedia/commons/7/73/Pale_Blue_Dot.png"
    },
    {
      "url": "https://upload.wikimedia.org/wikipedia/commons/a/a9/Example.jpg"
    }
  ],
  "format": "webp",
  "maxWidth": 800
}' |
apify call tamatama/bulk-image-downloader --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tamatama/bulk-image-downloader"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/e7NFypQYiRzlbekeH/builds/qxiriSVKN6LYbB3Z9/openapi.json
