# HEIC to JPG Converter: iPhone Photos in Bulk (`maliksaid/heic-to-jpg-converter`) Actor

Convert HEIC and HEIF photos from iPhone and iPad to JPEG, PNG, WebP or AVIF in bulk. Resize to presets, compress to a quality or file size target, and strip GPS and EXIF data. Returns file links and sizes. No upload to outside services.

- **URL**: https://apify.com/maliksaid/heic-to-jpg-converter.md
- **Developed by:** [Malik Said](https://apify.com/maliksaid) (community)
- **Categories:** Developer tools, Automation, E-commerce
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.20 / 1,000 photo converteds

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

## HEIC to JPG Converter: iPhone Photos to JPEG, PNG, WebP

HEIC to JPG Converter turns HEIC and HEIF photos from iPhone and iPad into JPEG, PNG, WebP or AVIF in one run. It can also resize them to web, product or social media sizes and compress them to a quality or file size target. GPS location and other EXIF data are removed. You get a download link for every converted file and one dataset row per photo.

### What it does

- Converts HEIC and HEIF photos (iPhone, iPad, many Android phones and cameras) to JPEG, PNG, WebP or AVIF.
- Handles the standard 12 MP and 24 MP phone photos, and files with several images inside (bursts, edits). The first image is used.
- Keeps the photo upright. The decoder applies the rotation and mirroring stored in the file.
- Also converts JPEG, PNG, WebP, AVIF, GIF and TIFF files in the same run.
- Resizes to presets: thumbnail, web, product square, Instagram, story, link preview and 16:9, or to your own size.
- Compresses to a quality from 1 to 100, or to a maximum file size in kilobytes.
- Removes GPS location, camera details and other metadata. The row says whether the original had GPS data, but never saves the position.

Decoding runs inside the container with [libheif](https://github.com/strukturag/libheif) and [sharp](https://sharp.pixelplumbing.com/). Your photos go to no outside service.

### Input: photo URLs or your own files

You can give photos in three ways:

1. **HEIC or image URLs.** Paste links, one per line. The Actor reads robots.txt on each host and skips files it does not allow.
2. **Key-value store.** Upload HEIC files to one of your Apify key-value stores and pick it in the input. The Actor only reads that store.
3. **Dataset.** Pick a dataset with photo links, for example from a scraper, and name the field that holds the link (default `imageUrl`). The Actor only reads the dataset.

With no input, the Actor converts one built-in sample HEIC photo, so a test run always works.

### How to convert HEIC to JPG in bulk

1. Paste your HEIC links in **HEIC or image URLs**, or pick a key-value store with your files.
2. Pick the **Output format**. JPEG opens everywhere.
3. Optional: pick a **Resize preset**, or set a **Maximum file size**.
4. Click **Start**.
5. Open the **Converted files** tab, or use the `outputUrl` link in each dataset row.

### Output

Each converted file is saved in the run's key-value store. Each photo also gets one dataset row.

| Field | Meaning |
|---|---|
| `status` | `ok` or `failed` |
| `error` | Why the photo failed. Null when it worked |
| `source` | `url`, `keyValueStore` or `sample` |
| `imageUrl` / `keyValueStoreKey` | Where the photo came from |
| `inputFormat`, `inputWidth`, `inputHeight`, `inputBytes` | The photo before conversion. HEIC and HEIF show as `heif` |
| `outputFormat`, `outputWidth`, `outputHeight`, `outputBytes` | The file after conversion |
| `bytesSaved`, `savedPercent` | Size change. Negative when the new file is larger, which is normal for HEIC to JPEG |
| `quality` | Quality used for JPEG, WebP or AVIF |
| `targetKilobytes`, `targetMet` | The size target and whether the file fits it |
| `hadExif`, `hadGps` | Whether the input had EXIF data and a GPS position |
| `metadataStripped` | Whether metadata was removed from the output |
| `outputKey`, `outputUrl` | Key and download link of the converted file |

Turn on **One ZIP with all photos** to also get one ZIP file with every converted photo. The ZIP is free. Its link is in the run's `OUTPUT` record (`zipUrl`) and in the status message. One ZIP holds up to 1,000 photos or 2 GB; photos past that stay available through their own links.

Sample record from a local test run (a 24 MP HEIC photo of NASA's "The Blue Marble", public domain, converted to JPEG):

```json
{
    "status": "ok",
    "source": "keyValueStore",
    "keyValueStoreKey": "p24",
    "inputFormat": "heif",
    "inputWidth": 5712,
    "inputHeight": 4284,
    "inputBytes": 1680702,
    "outputFormat": "jpeg",
    "outputWidth": 5712,
    "outputHeight": 4284,
    "outputBytes": 2649119,
    "bytesSaved": -968417,
    "savedPercent": -57.6,
    "quality": 90,
    "resizePreset": "none",
    "hadExif": true,
    "hadGps": false,
    "metadataStripped": true,
    "outputKey": "p24-8a5778f0.jpg",
    "contentType": "image/jpeg",
    "processingMs": 2809
}
```

The record has more fields, such as `id`, `outputUrl` and `scrapedAt`. Fields that do not apply are null.

### Resize presets

| Preset | Size | How it fits |
|---|---|---|
| Thumbnail | 300 x 300 | Fits inside, keeps proportions |
| Web medium | 1280 wide | Fits inside |
| Web large | 1920 wide | Fits inside |
| Product square | 1000 x 1000 | Whole image on a square background |
| Product large | 2000 x 2000 | Whole image on a square background |
| Instagram square | 1080 x 1080 | Fills the frame, trims the edges |
| Instagram portrait | 1080 x 1350 | Fills the frame, trims the edges |
| Story | 1080 x 1920 | Fills the frame, trims the edges |
| Link preview | 1200 x 630 | Fills the frame, trims the edges |
| Wide 16:9 | 1600 x 900 | Fills the frame, trims the edges |
| Custom size | Your width and height | Your choice of fit |

### Input options

| Option | Default | Meaning |
|---|---|---|
| HEIC or image URLs | None (sample photo) | Links to the photos |
| Key-value store | None | Your store with HEIC or image files |
| Dataset with photo links | None | A dataset from another run. Links come from **Dataset field with the link** (default `imageUrl`) |
| Record keys | All | Only these records of the store |
| Output format | JPEG | JPEG, PNG, WebP or AVIF |
| Quality | 90 | 1 to 100 for JPEG, WebP and AVIF |
| Lossless | Off | Exact pixels for WebP and AVIF |
| Maximum file size (KB) | 0 (none) | Lowers quality until the file fits, down to 20 |
| Resize preset | No resize | See the table above |
| Strip metadata | On | Removes EXIF, GPS, XMP and IPTC data. HEIC output always has none |
| Maximum input file size | 25 MB | The limit is 50 MB |
| Maximum image size | 40 megapixels | Larger images are skipped |
| Maximum images | 100 | Set 0 for no limit. One run converts at most 5,000 |
| One ZIP with all photos | Off | Also save one ZIP with every converted photo. Free |

### Limits and memory

- Runs use 512 MB. HEIC photos up to 30 megapixels are converted. That covers 12 MP and 24 MP phone photos. A 48 MP photo is skipped with a clear message. Pick a resize preset and JPEG, PNG or WebP output for large files.
- AVIF output at full size needs more memory. For photos above about 15 megapixels, pick a resize preset with AVIF.
- Photos are converted one at a time. In tests, a 12 MP photo took about 1.5 seconds and a 24 MP photo about 3 seconds.
- Failed photos are saved with the reason and are not charged.

### Pricing

You pay per converted photo, plus a small start fee per run. Failed photos are free. See the **Pricing** tab for current prices.

### Privacy and your files

You choose the photos. You must have the right to process them. Photos are processed in the run and stored only in your own Apify storage. GPS positions are never written to the dataset.

### FAQ

**Why is my JPEG larger than the HEIC file?**
HEIC compresses better than JPEG. A JPEG at quality 90 is often 30 to 60 percent larger. Lower the quality, or set a maximum file size.

**Can I keep the EXIF data?**
Not for HEIC input. The decoder returns pixels only, so HEIC output never carries EXIF or GPS data. For JPEG, PNG and other inputs, turn off **Strip metadata** to keep it.

**Does it work with Live Photos?**
It converts the still photo. The video part of a Live Photo is a separate file and is not converted.

**Can I call it from an AI agent or an automation?**
Yes. Call the Actor through the Apify API, MCP, Make, n8n or Zapier with a list of URLs, then read the dataset or the files.

# Actor input Schema

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

Links to HEIC or HEIF photos (iPhone and iPad photos), one per line. JPEG, PNG, WebP, AVIF, GIF and TIFF also work. The Actor reads robots.txt on each host and skips files it does not allow. Leave empty, with no key-value store, to convert a built-in sample HEIC photo.

## `keyValueStoreId` (type: `string`):

Optional. Pick one of your key-value stores that holds uploaded HEIC or image files. The Actor only reads this store. Converted files go to the run's own key-value store.

## `keyValueStoreKeys` (type: `array`):

Optional. Convert only these records of the store. Leave empty to convert every image record in the store.

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

Optional. A dataset with photo links, for example the output of a scraper. The Actor only reads it. Pick the field that holds the link below.

## `datasetField` (type: `string`):

Name of the top-level field that holds the photo link in each dataset row. Rows without an http or https link in this field are skipped and counted in the log.

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

Format of the converted files. JPEG opens everywhere. PNG is lossless. WebP and AVIF are smaller and suit websites.

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

Set the quality for JPEG, WebP and AVIF, from 1 to 100. Lower values give smaller files. 85 to 92 looks the same as the original for most photos. PNG is always lossless.

## `lossless` (type: `boolean`):

Keep every pixel exactly for WebP and AVIF. Files get much larger. Use it for logos and screenshots.

## `maxOutputKilobytes` (type: `integer`):

Optional size target for each file, in kilobytes. The Actor lowers the quality step by step until the file fits, down to quality 20. 0 means no target. Works for WebP, AVIF and JPEG.

## `resizePreset` (type: `string`):

Pick a target size. 'Padded' keeps the whole image on a square of the background colour. 'Cropped' fills the frame and trims the edges. Pick 'Custom size' to set width, height and fit below.

## `width` (type: `integer`):

Set the width in pixels for 'Custom size'. Leave empty to scale by height only.

## `height` (type: `integer`):

Set the height in pixels for 'Custom size'. Leave empty to scale by width only.

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

Choose how 'Custom size' fits the image into the width and height.

## `allowEnlargement` (type: `boolean`):

Let the Actor make small images bigger to reach the preset size. Off keeps small images at their own size.

## `backgroundColor` (type: `string`):

Set the colour for padding and for transparent areas in JPEG output, as #rrggbb.

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

Remove EXIF, GPS location, camera details, XMP and IPTC data from every output file. On by default to protect privacy. HEIC files always lose their metadata, because the decoder returns pixels only; the row still says whether the original had GPS data.

## `keepColorProfile` (type: `boolean`):

Keep the embedded ICC colour profile when metadata is stripped. Off converts colours to sRGB, which every browser shows correctly.

## `maxImageMegabytes` (type: `integer`):

Skip input files larger than this. The limit is 50 MB.

## `maxMegapixels` (type: `integer`):

Skip images with more pixels than this. HEIC photos are decoded up to 25 megapixels per 512 MB of run memory, which covers 12 and 24 MP phone photos. The row says why when an image is skipped.

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

Stop after converting this many files. The default keeps test runs short and cheap. Set 0 for no limit. One run converts at most 5,000 files.

## `createZip` (type: `boolean`):

Also save one ZIP file with every converted photo, for a single download. Free. Up to 1,000 photos or 2 GB per ZIP. The link is in the OUTPUT record and the status message.

## Actor input object example

```json
{
  "imageUrls": [],
  "keyValueStoreKeys": [],
  "datasetField": "imageUrl",
  "outputFormat": "jpeg",
  "quality": 90,
  "lossless": false,
  "maxOutputKilobytes": 0,
  "resizePreset": "none",
  "fit": "inside",
  "allowEnlargement": false,
  "backgroundColor": "#ffffff",
  "stripMetadata": true,
  "keepColorProfile": false,
  "maxImageMegabytes": 25,
  "maxMegapixels": 40,
  "maxItems": 100,
  "createZip": false
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `files` (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 = {
    "imageUrls": [],
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("maliksaid/heic-to-jpg-converter").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": [],
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("maliksaid/heic-to-jpg-converter").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": [],
  "maxItems": 100
}' |
apify call maliksaid/heic-to-jpg-converter --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,maliksaid/heic-to-jpg-converter"
        }
    }
}
```

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/mYVkrgMjUUoIqCIX2/builds/eEiS3Td4AMpNFx8s8/openapi.json
