# Bulk Image Compressor (`automation-lab/bulk-image-compressor`) Actor

Compress batches of image URLs or uploaded files into smaller, publishing-ready assets with before-and-after metadata.

- **URL**: https://apify.com/automation-lab/bulk-image-compressor.md
- **Developed by:** [Stas Persiianenko](https://apify.com/automation-lab) (community)
- **Categories:** Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.30 / 1,000 item extracteds

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Bulk Image Compressor

Compress batches of public image URLs or private files from an Apify key-value store into smaller, publishing-ready assets.

This **bulk image compressor** converts JPEG, PNG, WebP, and AVIF inputs, can resize them, strips metadata by default, and writes every optimized file to the run's key-value store. The dataset records before-and-after MIME type, dimensions, byte size, compression ratio, settings, stored-file key, and per-item status.

Use it when recurring site, catalog, marketplace, or content images need a reproducible optimization step before publishing.

### What can Bulk Image Compressor do?

- Process a list of anonymously reachable direct image URLs.
- Accept one convenient file-picker upload.
- Process many private files by key from an Apify key-value store.
- Convert output to WebP, JPEG, PNG, or AVIF.
- Resize without enlarging the original.
- Search for the highest lossy quality that fits a target file size.
- Strip EXIF and other metadata by default.
- Preserve metadata when explicitly requested.
- Save optimized binary files in the run's default key-value store.
- Return one typed success or failure record per input.
- Charge the per-image event only for successful compression.

No browser, residential proxy, or third-party image-processing API is used. Images are processed inside the Actor container with libvips.

### Who is this image optimizer for?

**Ecommerce teams** can standardize supplier and catalog images before product imports.

**SEO and performance teams** can produce WebP or AVIF assets for page-speed work and Core Web Vitals improvements.

**Content operations teams** can prepare recurring editorial image batches without manually opening a desktop application.

**Developers and automation builders** can add an image optimization step to Apify Tasks, schedules, API workflows, Make, Zapier, or custom pipelines.

**Privacy-conscious teams** can use private key-value store records rather than exposing source files on a public host.

### Why use this Actor?

Unlike a basic converter that only returns a file, each input receives an audit record with the original and output sizes, dimensions, actual encoder quality, reduction percentage, storage location, and failure reason.

The Actor also supports two distinct source routes:

1. direct public URLs for simple automation;
2. Apify key-value store records for private or bulk-uploaded files.

A failed source does not discard successful work from the same batch. Failed inputs remain visible in the dataset and are not charged the per-image event.

### Supported image formats

| Input | Output | Notes |
| --- | --- | --- |
| JPEG | JPEG, PNG, WebP, AVIF | JPEG output uses MozJPEG settings |
| PNG | JPEG, PNG, WebP, AVIF | Transparent pixels are flattened on white for JPEG |
| WebP | JPEG, PNG, WebP, AVIF | Static images only |
| AVIF | JPEG, PNG, WebP, AVIF | Static images only |

Animated images, SVG, TIFF, PDF, and raw camera formats are not supported.

Each source is limited to 25 MB and 40 million decoded pixels. A run accepts at most 500 sources.

### How to compress bulk images

1. Open the Actor input page.
2. Add direct image URLs, upload one image, or enter a key-value store ID and record keys.
3. Choose WebP, JPEG, PNG, or AVIF output.
4. Set quality and optional maximum dimensions.
5. Optionally set a target size in KB for JPEG, WebP, or AVIF.
6. Leave metadata stripping enabled unless the output must preserve metadata.
7. Run the Actor.
8. Open **Compression results** for the audit rows.
9. Open **Optimized image files** to download or integrate the binary outputs.

The default prefill uses a real Wikimedia Commons image and produces a useful WebP file without any setup.

### Input parameters

| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| `imageUrls` | string array | real Wikimedia URL | Public direct HTTP(S) image URLs |
| `uploadedImage` | string/file picker | none | One uploaded file or selected KVS file URL |
| `sourceKeyValueStoreId` | string | default run store | Store ID or name containing private source files |
| `sourceKeys` | string array | empty | Binary image record keys in the source store |
| `outputFormat` | enum | `webp` | `webp`, `jpeg`, `png`, or `avif` |
| `quality` | integer | `80` | Quality from 1 to 100 for lossy encoders |
| `targetSizeKb` | integer | none | Optional 5–25,000 KB target for lossy output |
| `maxWidth` | integer | none | Maximum width; never enlarges |
| `maxHeight` | integer | none | Maximum height; never enlarges |
| `fit` | enum | `inside` | Resize fit when dimensions are set |
| `stripMetadata` | boolean | `true` | Remove EXIF and ancillary metadata |
| `maxItems` | integer | `20` | Maximum sources processed, up to 500 |

At least one of `imageUrls`, `uploadedImage`, or `sourceKeys` is required.

`targetSizeKb` is intentionally unavailable for PNG because PNG's lossless encoder does not expose a quality search equivalent to JPEG, WebP, or AVIF.

### Bulk private-file workflow

For recurring private batches:

1. Create or select an Apify key-value store.
2. Upload image records to that store.
3. Put its ID or name in `sourceKeyValueStoreId`.
4. Put every binary record key in `sourceKeys`.
5. Run the Actor.

The Actor reads source bytes from that store and writes optimized files to the current run's default store. It does not make private inputs public.

For a single file, the `uploadedImage` file picker is the quickest route.

### Target-size compression

Set `targetSizeKb` when a publishing system imposes a byte budget such as 100 KB.

For JPEG, WebP, and AVIF, the Actor performs a bounded binary search from quality 1 up to the requested `quality`. It keeps the highest tested quality whose output fits the requested byte target.

The result row reports the quality actually used. Very complex images may still exceed a very small target even at quality 1; inspect `outputBytes` rather than assuming every target is achievable.

Resizing before target-size search often improves visual quality at the same byte budget.

### Output dataset

Each source produces one dataset item. Important fields include:

| Field | Description |
| --- | --- |
| `status` | `succeeded` or `failed` |
| `error` | Failure reason, otherwise `null` |
| `sourceType` | `url` or `keyValueStore` |
| `source` / `sourceKey` | Input identifier |
| `originalMimeType` / `outputMimeType` | Before-and-after formats |
| `originalWidth` / `originalHeight` | Source dimensions |
| `outputWidth` / `outputHeight` | Result dimensions |
| `originalBytes` / `outputBytes` | Before-and-after sizes |
| `compressionRatio` | Output bytes divided by original bytes |
| `sizeReductionPercent` | Positive when smaller; negative when output grew |
| `quality` | Encoder quality actually used |
| `storedFileKey` | Optimized file record key |
| `storedFileUrl` | Cloud API record URL |
| `processedAt` | Completion timestamp |

Nullable values are expected on failed records because no valid image metadata or output exists.

### Example output

A local run against the 1920-pixel Wikimedia Fronalpstock image produced this representative record:

```json
{
  "sourceType": "url",
  "status": "succeeded",
  "originalFileName": "1920px-Fronalpstock_big.jpg",
  "originalMimeType": "image/jpeg",
  "originalWidth": 1920,
  "originalHeight": 863,
  "originalBytes": 460422,
  "outputMimeType": "image/webp",
  "outputWidth": 1200,
  "outputHeight": 539,
  "outputBytes": 101826,
  "compressionRatio": 0.2212,
  "sizeReductionPercent": 77.88,
  "outputFormat": "webp",
  "quality": 75,
  "metadataStripped": true,
  "storedFileKey": "compressed-001-1920px-Fronalpstock_big-166807a3de",
  "error": null
}
```

Exact byte size depends on input pixels, output format, dimensions, quality, and metadata settings.

### Optimized file storage

Successful files are written under keys beginning with `compressed-` in the run's default key-value store.

`outputFileName` includes the correct extension for human-facing exports. `storedFileKey` is the exact storage record key, while the record's content type identifies the binary format.

Cloud dataset rows include `storedFileUrl`. Private stores still require the caller's Apify authorization when accessed through the API.

Failed sources do not create file records.

### How much does it cost to compress bulk images?

This Actor uses pay per event:

- one small `start` event per run;
- one `item` event for each successfully compressed image;
- no per-image charge for failed inputs.

The one-time `start` event is **$0.00005**. Successful images use this decreasing tier curve:

| Tier | Price per successfully compressed image |
| --- | ---: |
| FREE | $0.002484 |
| BRONZE | $0.00216 |
| SILVER | $0.0016848 |
| GOLD | $0.001296 |
| PLATINUM | $0.000864 |
| DIAMOND | $0.0006048 |

At BRONZE, 1 successful image costs $0.00221 including the start event, 10 cost $0.02165, and 100 cost $0.21605. Your account tier is shown in Apify Console before a run starts.

Because failed inputs are included in the dataset but do not emit an `item` charge, dataset row count can be higher than charged image count.

### Failure behavior and retries

The Actor retries network errors, HTTP 429, and temporary 5xx responses up to two times after the first request.

It does not repeatedly retry deterministic bad input, missing key-value store records, private-network URLs, non-image responses, unsupported formats, oversized files, or decoding errors.

Individual source errors create `failed` rows and let the batch continue. Invalid run-level settings or an input with no sources fail the run with a non-zero exit.

Redirect destinations are revalidated. URLs resolving to localhost, private IP ranges, or link-local networks are rejected to prevent server-side request forgery.

### Performance and limits

Processing is sequential to keep memory predictable at the configured 256 MB allocation.

AVIF and target-size searches take longer than a single WebP or JPEG encode because they perform more encoder work.

For best throughput:

- resize very large originals when full resolution is unnecessary;
- prefer WebP for a balance of speed and compression;
- reserve AVIF for workflows where extra encoding time is acceptable;
- split batches approaching 500 images across scheduled runs;
- use KVS records when source hosts apply download restrictions.

The Actor does not use proxies automatically.

### Integrations and automation patterns

**Scheduled catalog optimization:** upload a supplier batch to a key-value store, run this Actor, then pass successful file URLs and metadata to a publishing workflow.

**Website migration:** convert legacy JPEG and PNG asset lists to WebP while retaining an audit dataset for size savings.

**Content pipeline:** trigger the Actor after editorial image approval and send optimized files to a CMS connector.

**Quality budget enforcement:** set maximum dimensions and `targetSizeKb`, then filter dataset rows whose `outputBytes` still exceed the desired budget.

**Error queue:** route `status=failed` rows to a retry or operator review step without reprocessing successful images.

### Run through the Apify API with cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~bulk-image-compressor/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "imageUrls": [
      "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Fronalpstock_big.jpg/1920px-Fronalpstock_big.jpg"
    ],
    "outputFormat": "webp",
    "quality": 75,
    "maxWidth": 1200
  }'
```

Poll the returned run, then read its default dataset and key-value store links.

### Run with JavaScript

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/bulk-image-compressor').call({
  imageUrls: [
    'https://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Fronalpstock_big.jpg/1920px-Fronalpstock_big.jpg',
  ],
  outputFormat: 'avif',
  quality: 85,
  targetSizeKb: 100,
  maxWidth: 1400,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

Use `run.defaultKeyValueStoreId` to retrieve successful binary files by `storedFileKey`.

### Run with Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/bulk-image-compressor').call(run_input={
    'imageUrls': [
        'https://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Fronalpstock_big.jpg/1920px-Fronalpstock_big.jpg'
    ],
    'outputFormat': 'webp',
    'quality': 80,
    'maxWidth': 1600,
    'stripMetadata': True,
})

items = client.dataset(run['defaultDatasetId']).list_items().items
print(items)
```

The dataset tells the next step which record keys were created.

### Use Bulk Image Compressor with MCP

Add this Actor to Claude Code:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/bulk-image-compressor"
```

**Claude Desktop**, **Cursor**, and **VS Code** can use the equivalent remote MCP server configuration:

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

Example prompts:

- “Compress these three public image URLs to WebP at quality 80 and show the byte savings.”
- “Resize the images in my supplied Apify store to 1200 pixels wide and save AVIF outputs.”
- “Try to fit this photo under 100 KB and tell me the actual quality and output size.”

Do not put private tokens or credentials into prompt text.

### Legality and responsible use

Only process images you own, are authorized to transform, or may lawfully download and reuse.

A publicly reachable image URL does not automatically grant copyright permission. Respect source-site terms, licenses, attribution requirements, and applicable law.

Metadata stripping can remove location and camera data from outputs, but the original source remains unchanged. Preserve metadata only when the workflow needs it and users understand the privacy impact.

The Actor rejects private-network URLs and does not support authenticated website scraping. Use key-value store inputs for private files rather than embedding temporary credentials in URLs.

### FAQ and troubleshooting

#### Why did my output become larger?

Format conversion is not guaranteed to reduce every file. Tiny optimized PNGs, flat illustrations, or an output quality that is too high can grow. Check `sizeReductionPercent`, lower quality, choose WebP or AVIF, or set dimensions appropriate for publishing.

#### Why is `storedFileUrl` null locally?

Local `apify run` storage does not have a cloud key-value store ID. The stored file still exists under local Actor storage. Cloud runs include the API URL.

#### Why did an image fail with a private-network error?

The source hostname resolved to a local, private, or link-local address. Upload the authorized image to an Apify key-value store and use `sourceKeys` instead.

#### Why did target-size compression miss the target?

The target may be impossible at quality 1 for the selected dimensions and format. Reduce dimensions, choose AVIF or WebP, or increase `targetSizeKb`.

#### Are failed images charged?

No. They produce dataset status rows for diagnosis but do not emit the per-image `item` charge.

#### Does the Actor remove backgrounds?

No. Background removal is a different computer-vision workflow and is not claimed by this Actor. JPEG conversion only flattens existing transparency onto white.

### Related Actors

For workflows that first discover image asset URLs on public pages, see [Web Image Downloader](https://apify.com/automation-lab/web-image-downloader).

For broader website asset inventories, this Actor can consume direct image URLs produced by another authorized automation step, then return optimized binaries and before/after metadata.

### Changelog

See the Actor's **Changelog** tab for customer-visible capability updates.

# Actor input Schema

## `imageUrls` (type: `array`):

Publicly reachable direct HTTP(S) URLs of JPEG, PNG, WebP, or AVIF files. Uploaded-file URLs from Apify's file picker also work here.

## `uploadedImage` (type: `string`):

Upload one image or select an existing key-value store file. For bulk private files, use the store ID and record keys below.

## `sourceKeyValueStoreId` (type: `string`):

Optional Apify key-value store ID or name containing private input images. Leave empty to read sourceKeys from the run's default store.

## `sourceKeys` (type: `array`):

Record keys of binary images in the selected key-value store. This is the bulk upload/private-file route.

## `outputFormat` (type: `string`):

Format used for every optimized output file.

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

Encoder quality from 1 to 100 for JPEG, WebP, and AVIF. With targetSizeKb, this is the highest quality considered.

## `targetSizeKb` (type: `integer`):

Optional maximum output size per image for JPEG, WebP, or AVIF. The Actor searches for the highest quality that fits when possible.

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

Optional maximum output width in pixels. Images are never enlarged.

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

Optional maximum output height in pixels. Images are never enlarged.

## `fit` (type: `string`):

How an image fits when both maximum dimensions are set.

## `stripMetadata` (type: `boolean`):

Remove EXIF and other metadata to reduce size and avoid carrying location or camera details into published assets.

## `maxItems` (type: `integer`):

Maximum number of image sources processed in this run.

## Actor input object example

```json
{
  "imageUrls": [
    "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Fronalpstock_big.jpg/1920px-Fronalpstock_big.jpg"
  ],
  "sourceKeys": [],
  "outputFormat": "webp",
  "quality": 80,
  "fit": "inside",
  "stripMetadata": true,
  "maxItems": 20
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset containing one status and before/after metadata record per input image.

## `files` (type: `string`):

Key-value store containing successfully compressed files.

# 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 = {
    "imageUrls": [
        "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Fronalpstock_big.jpg/1920px-Fronalpstock_big.jpg"
    ],
    "outputFormat": "webp"
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/bulk-image-compressor").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 = {
    "imageUrls": ["https://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Fronalpstock_big.jpg/1920px-Fronalpstock_big.jpg"],
    "outputFormat": "webp",
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/bulk-image-compressor").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 '{
  "imageUrls": [
    "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Fronalpstock_big.jpg/1920px-Fronalpstock_big.jpg"
  ],
  "outputFormat": "webp"
}' |
apify call automation-lab/bulk-image-compressor --silent --output-dataset

```

## MCP server setup

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

```

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/JKHoea9OxPXkCogcu/builds/saFrBxIyjYuxrBR6l/openapi.json
