---
title: Images
description: Images is a platform for creating scalable and reliable image pipelines, designed to help developers deploy media-rich applications faster.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Images

Create scalable and reliable image pipelines without managing complex infrastructure.

 Available on Free and Paid plans 

Images is designed to help developers deploy media-rich applications faster.

With Images, you can dynamically resize, optimize, and manipulate images at Cloudflare's edge to serve the optimal version for each user in real time — without manually creating or storing multiple copies of the same image for different use cases, browsers, or device breakpoints.

![Use Images to optimize images for delivery](https://developers.cloudflare.com/_astro/overview.SNb8PIJv_1fDdXN.webp) 

## Get started

Cloudflare offers two integration paths for Images:

[ Bring your own storage ](https://developers.cloudflare.com/images/optimization/transformations/overview/) Transform and deliver images that are stored on any origin, including S3-compatible buckets like R2. 

[ Use Images to host your assets ](https://developers.cloudflare.com/images/optimization/hosted-images/) Upload directly to Images for a fully managed solution to handle storage, optimization, and delivery. 

If you’re new to Images, start here to learn the essentials:

* Understand how [image optimization](https://developers.cloudflare.com/images/get-started/introduction/) improves your user experience and website performance.
* Familiarize yourself with the [terminology](https://developers.cloudflare.com/images/get-started/key-concepts/) used through our documentation.
* Read about the [limits and supported formats](https://developers.cloudflare.com/images/optimization/features/) for image inputs and outputs.

---

## Features

###  Optimization 

Browse the various features for compressing, cropping, resizing, and manipulating images.

[ Use optimization ](https://developers.cloudflare.com/images/optimization/features/) 

###  Flows 

Create transformation flows to configure automated rules for optimizing remote images on your zone.

[ Use flows ](https://developers.cloudflare.com/images/optimization/features) 

###  Storage 

Learn how Images can streamline uploads in your image pipeline.

[ Use Images API ](https://developers.cloudflare.com/images/storage/upload-images/methods) 

###  Predefined variants 

Create predefined variants to specify how a hosted image should be resized on request.

[ Use variants ](https://developers.cloudflare.com/images/optimization/hosted-images/create-variants/) 

---

## More resources

[Pricing](https://developers.cloudflare.com/images/pricing/) 

Learn about Images pricing.

[Community](https://community.cloudflare.com/c/developers/images/63) 

Engage with other users and the Images team on Community forum.

[Discord](https://discord.cloudflare.com) 

Ask questions, show what you're building, and discuss the platform with other developers.

[@CloudflareDev](https://x.com/cloudflaredev) 

Follow @CloudflareDev on Twitter to learn about product announcements from the Developer Platform.

```json
{"@context":"https://schema.org","@type":"WebPage","@id":"https://developers.cloudflare.com/images/#page","headline":"Overview · Cloudflare Images docs","description":"Images is a platform for creating scalable and reliable image pipelines, designed to help developers deploy media-rich applications faster.","url":"https://developers.cloudflare.com/images/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-05-26","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}}]}
```

---

---
title: Demos and architectures
description: Explore demo applications and reference architectures for Cloudflare Images.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Demos and architectures

Learn how you can use Images within your existing architecture.

## Reference architectures

Explore the following reference architectures that use Images:

[Designing a distributed web performance architectureA prescriptive pattern for building a Cloudflare-based L7 performance architecture that reduces latency, raises cache efficiency, and improves Core Web Vitals.](https://developers.cloudflare.com/reference-architecture/diagrams/content-delivery/distributed-web-performance-architecture/)[Fullstack applicationsA practical example of how these services come together in a real fullstack application architecture.](https://developers.cloudflare.com/reference-architecture/diagrams/serverless/fullstack-application/)[Optimizing image delivery with Cloudflare image resizing and R2Learn how to get a scalable, high-performance solution to optimizing image delivery.](https://developers.cloudflare.com/reference-architecture/diagrams/content-delivery/optimizing-image-delivery-with-cloudflare-image-resizing-and-r2/)

```json
{"@context":"https://schema.org","@type":"WebPage","@id":"https://developers.cloudflare.com/images/demos/#page","headline":"Demos and architectures · Cloudflare Images docs","description":"Explore demo applications and reference architectures for Cloudflare Images.","url":"https://developers.cloudflare.com/images/demos/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-05-19","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/demos/","name":"Demos and architectures"}}]}
```

---

---
title: Pricing
description: Cloudflare Images pricing for transformations, storage, and delivery on Free and Paid plans.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Pricing

By default, all users are on the Images Free plan. The Free plan includes access to the transformations feature, which lets you optimize images stored outside of Images, like in [R2](https://developers.cloudflare.com/r2/).

The Paid plan allows transformations, as well as access to storage in Images.

Pricing is dependent on which features you use. The table below shows which metrics are used for each use case.

| Use case                                             | Metrics                         | Availability        |
| ---------------------------------------------------- | ------------------------------- | ------------------- |
| Optimize images stored outside of Images             | Images Transformed              | Free and Paid plans |
| Optimize images that are stored in Cloudflare Images | Images Stored, Images Delivered | Only Paid plans     |

## Images Free

On the Free plan, you can request up to 5,000 unique transformations each month for free.

Once you exceed 5,000 unique transformations:

* Existing transformations in cache will continue to be served as expected.
* New transformations will return a `9422` error. If your source image is from the same domain where the transformation is served, then you can use the [onerror parameter](https://developers.cloudflare.com/images/optimization/features/#onerror) to redirect to the original image.
* You will not be charged for exceeding the limits in the Free plan.

To request more than 5,000 unique transformations each month, you can purchase an Images Paid plan.

## Images Paid

When you purchase an Images Paid plan, you can choose your own storage or add storage in Images.

| Metric             | Pricing                                                                                    |
| ------------------ | ------------------------------------------------------------------------------------------ |
| Images Transformed | First 5,000 unique transformations included + $0.50 / 1,000 unique transformations / month |
| Images Stored      | $5 / 100,000 images stored / month                                                         |
| Images Delivered   | $1 / 100,000 images delivered / month                                                      |

If you optimize an image stored outside of Images, then you will be billed only for Images Transformed.

Images Stored and Images Delivered apply only to images that are stored in your Images bucket. When you optimize a hosted image through the image delivery URL, then this counts toward Images Delivered — not Images Transformed. However, if you optimize a hosted image through the Images binding, then this counts toward Images Transformed.

## Metrics

### Images Transformed

A unique transformation is a request to transform an original image based on a set of [supported parameters](https://developers.cloudflare.com/images/optimization/features/). This metric is used when using the Images binding or optimizing images that are stored outside of Images. When using the [Images binding](https://developers.cloudflare.com/images/optimization/binding/) in Workers, every call to the binding counts as a transformation, regardless of whether the image or parameters are unique.

For example, if you transform `thumbnail.jpg` as 100x100, then this counts as one unique transformation. If you transform the same `thumbnail.jpg` as 200x200, then this counts as a separate unique transformation.

You are billed on the number of unique transformations that are requested within each calendar month. Repeat requests for the same transformation within the same month are counted only once for that month.

The `format` parameter counts as only one billable transformation, even if multiple copies of an image are served. In other words, if `width=100,format=auto/thumbnail.jpg` is served to some users as AVIF and to others as WebP, then this counts as one unique transformation instead of two.

#### Example #1

If you serve 2,000 remote images in five different sizes each month, then this results in 10,000 unique transformations. Your estimated cost for the month would be:

|                 | Usage                                                 | Included | Billable quantity | Price                         |
| --------------- | ----------------------------------------------------- | -------- | ----------------- | ----------------------------- |
| Transformations | 10,000 unique transformations [1](#user-content-fn-5) | 5,000    | 5,000             | $2.50 [2](#user-content-fn-6) |

#### Example #2

If you use [R2](https://developers.cloudflare.com/r2/) for storage then your estimated monthly costs will be the sum of your monthly Images costs and monthly [R2 costs](https://developers.cloudflare.com/r2/pricing/#storage-usage).

For example, if you upload 5,000 images to R2 with an average size of 5 MB, and serve 2,000 of those images in five different sizes, then your estimated cost for the month would be:

|                    | Usage                                                 | Included   | Billable quantity | Price                           |
| ------------------ | ----------------------------------------------------- | ---------- | ----------------- | ------------------------------- |
| Storage            | 25 GB [3](#user-content-fn-1)                         | 10 GB      | 15 GB             | $0.22 [4](#user-content-fn-7)   |
| Class A operations | 5,000 writes [5](#user-content-fn-2)                  | 1 million  | 0                 | $0.00 [6](#user-content-fn-8)   |
| Class B operations | 10,000 reads [7](#user-content-fn-3)                  | 10 million | 0                 | $0.00 [8](#user-content-fn-9)   |
| Transformations    | 10,000 unique transformations [9](#user-content-fn-4) | 5,000      | 5,000             | $2.50 [10](#user-content-fn-10) |
| **Total**          |                                                       |            |                   | **$2.72**                       |

### Images Stored

Storage in Images is available only with an Images Paid plan. You can purchase storage in increments of $5 for every 100,000 images stored per month.

You can create predefined variants to specify how an image should be resized, such as `thumbnail` as 100x100 and `hero` as 1600x500.

Only uploaded images count toward Images Stored; defining variants will not impact your storage limit.

### Images Delivered

For images that are stored in Images, you will incur $1 for every 100,000 images delivered per month. This metric does not include transformed images that are stored in remote sources.

Every image requested by the browser counts as one billable request.

#### Example

A retail website has a product page that uses Images to serve 10 images. If the page was visited 10,000 times this month, then this results in 100,000 images delivered — or $1.00 in billable usage.

## Footnotes

1. 2,000 original images × 5 sizes [↩](#user-content-fnref-5)
2. (5,000 transformations / 1,000) × $0.50 [↩](#user-content-fnref-6)
3. 5,000 objects × 5 MB per object [↩](#user-content-fnref-1)
4. 15 GB × $0.015 / GB-month [↩](#user-content-fnref-7)
5. 5,000 objects × 1 write per object [↩](#user-content-fnref-2)
6. 0 × $4.50 / million requests [↩](#user-content-fnref-8)
7. 2,000 objects × 5 reads per object [↩](#user-content-fnref-3)
8. 0 × $0.36 / million requests [↩](#user-content-fnref-9)
9. 2,000 original images × 5 sizes [↩](#user-content-fnref-4)
10. (5,000 transformations / 1,000) × $0.50 [↩](#user-content-fnref-10)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/pricing/#page","headline":"Pricing · Cloudflare Images docs","description":"Cloudflare Images pricing for transformations, storage, and delivery on Free and Paid plans.","url":"https://developers.cloudflare.com/images/pricing/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-06-10","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/pricing/","name":"Pricing"}}]}
```

---

---
title: Cloudflare Polish
description: Cloudflare Polish automatically optimizes images by stripping metadata and applying lossy or lossless compression.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Cloudflare Polish

Cloudflare Polish is a one-click image optimization product that automatically optimizes images in your site. Polish strips metadata from images and reduces image size through lossy or lossless compression to accelerate the speed of image downloads.

When an image is fetched from your origin, our systems automatically optimize it in Cloudflare's cache. Subsequent requests for the same image will get the smaller, faster, optimized version of the image, improving the speed of your website.

![Example of Polish compression's quality.](https://developers.cloudflare.com/_astro/polish.DBlbPZoO_Zd4DDj.webp) 

## Comparison

* **Polish** automatically optimizes all images served from your origin server. It keeps the same image URLs, and does not require changing markup of your pages.
* **Cloudflare Images** API allows you to create new images with resizing, cropping, watermarks, and other processing applied. These images get their own new URLs, and you need to embed them on your pages to take advantage of this service. Images created this way are already optimized, and there is no need to apply Polish to them.

## Availability

|              | Free | Pro | Business | Enterprise |
| ------------ | ---- | --- | -------- | ---------- |
| Availability | No   | Yes | Yes      | Yes        |

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/polish/#page","headline":"Cloudflare Polish · Cloudflare Images docs","description":"Cloudflare Polish automatically optimizes images by stripping metadata and applying lossy or lossless compression.","url":"https://developers.cloudflare.com/images/polish/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/polish/","name":"Cloudflare Polish"}}]}
```

---

---
title: Activate Polish
description: Turn on Cloudflare Polish in the dashboard to automatically optimize images with lossy or lossless compression.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Activate Polish

Images in the [cache must be purged](https://developers.cloudflare.com/cache/how-to/purge-cache/) or expired before seeing any changes in Polish settings.

Warning

Do not activate Polish and [image transformations](https://developers.cloudflare.com/images/optimization/transformations/overview) simultaneously. Image transformations already apply lossy compression, which makes Polish redundant.

1. In the Cloudflare dashboard, go to the **Account home** page.  
[ Go to **Account home** ](https://dash.cloudflare.com/?to=/:account/home)
2. Select the domain where you want to activate Polish.
3. Select **Speed** \> **Settings** \> **Image Optimization**.
4. Under **Polish**, select _Lossy_ or _Lossless_ from the drop-down menu. [_Lossy_](https://developers.cloudflare.com/images/polish/compression/#lossy) gives greater file size savings.
5. (Optional) Select **WebP**. Enable this option if you want to further optimize PNG and JPEG images stored in the origin server, and serve them as WebP files to browsers that support this format.

To ensure WebP is not served from cache to a browser without WebP support, disable any WebP conversion utilities at your origin web server when using Polish.

Note

To use this feature on specific hostnames - instead of across your entire zone - use a [configuration rule](https://developers.cloudflare.com/rules/configuration-rules/).

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/polish/activate-polish/#page","headline":"Activate Polish · Cloudflare Images docs","description":"Turn on Cloudflare Polish in the dashboard to automatically optimize images with lossy or lossless compression.","url":"https://developers.cloudflare.com/images/polish/activate-polish/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/polish/","name":"Cloudflare Polish"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/polish/activate-polish/","name":"Activate Polish"}}]}
```

---

---
title: Cf-Polished statuses
description: Learn about Cf-Polished statuses in Cloudflare Images. Understand how to handle missing headers, optimize image formats, and troubleshoot common issues.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Cf-Polished statuses

If a `Cf-Polished` header is not returned, try [using single-file cache purge](https://developers.cloudflare.com/cache/how-to/purge-cache) to purge the image. The `Cf-Polished` header may also be missing if the origin is sending non-image `Content-Type`, or non-cacheable `Cache-Control`.

* `input_too_large`: The input image is too large or complex to process, and needs a lower resolution. Cloudflare recommends using PNG or JPEG images that are less than 4,000 pixels in any dimension, and smaller than 20 MB.
* `not_compressed` or `not_needed`: The image was fully optimized at the origin server and no compression was applied.
* `webp_bigger`: Polish attempted to convert to WebP, but the WebP image was not better than the original format. Because the WebP version does not exist, the status is set on the JPEG/PNG version of the response. Refer to [the reasons why Polish chooses not to use WebP](https://developers.cloudflare.com/images/polish/no-webp/).
* `cannot_optimize` or `internal_error`: The input image is corrupted or incomplete at the origin server. Upload a new version of the image to the origin server.
* `format_not_supported`: The input image format is not supported (for example, BMP or TIFF) or the origin server is using additional optimization software that is not compatible with Polish. Try converting the input image to a web-compatible format (like PNG or JPEG) and/or disabling additional optimization software at the origin server.
* `vary_header_present`: The origin web server has sent a `Vary` header with a value other than `accept-encoding`. If the origin web server is attempting to support WebP, disable WebP at the origin web server and let Polish perform the WebP conversion. Polish will still work if `accept-encoding` is the only header listed within the `Vary` header. Polish skips image URLs processed by [Cloudflare Images](https://developers.cloudflare.com/images/optimization/transformations/overview).

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/polish/cf-polished-statuses/#page","headline":"Cf-Polished statuses · Cloudflare Images docs","description":"Learn about Cf-Polished statuses in Cloudflare Images. Understand how to handle missing headers, optimize image formats, and troubleshoot common issues.","url":"https://developers.cloudflare.com/images/polish/cf-polished-statuses/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/polish/","name":"Cloudflare Polish"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/polish/cf-polished-statuses/","name":"Cf-Polished statuses"}}]}
```

---

---
title: Polish compression
description: Learn about Cloudflare's Polish compression options, including Lossless, Lossy, and WebP, to optimize image file sizes while managing metadata effectively.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Polish compression

With Lossless and Lossy modes, Cloudflare attempts to strip as much metadata as possible. However, Cloudflare cannot guarantee stripping all metadata because other factors, such as caching status, might affect which metadata is finally sent in the response.

Warning

Polish may not be applied to origin responses that contain a `Vary` header. The only accepted `Vary` header is `Vary: Accept-Encoding`.

## Compression options

### Off

Polish is disabled and no compression is applied. Disabling Polish does not revert previously polished images to original, until they expire or are purged from the cache.

### Lossless

The Lossless option attempts to reduce file sizes without changing any of the image pixels, keeping images identical to the original. It removes most metadata, like EXIF data, and losslessly recompresses image data. JPEG images may be converted to progressive format. On average, lossless compression reduces file sizes by 21 percent compared to unoptimized image files.

The Lossless option prevents conversion of JPEG to WebP, because this is always a lossy operation.

### Lossy

The Lossy option applies significantly better compression to images than the Lossless option, at a cost of small quality loss. When uncompressed, some of the redundant information from the original image is lost. On average, using Lossy mode reduces file sizes by 48 percent.

This option also removes metadata from images. The Lossy option mainly affects JPEG images, but PNG images may also be compressed in a lossy way, or converted to JPEG when this improves compression.

### WebP

When enabled, in addition to other optimizations, Polish creates versions of images converted to the WebP format.

WebP compression is quite effective on PNG images, reducing file sizes by approximately 26 percent. It may reduce file sizes of JPEG images by around 17 percent, but this [depends on several factors](https://developers.cloudflare.com/images/polish/no-webp/). WebP is supported in all browsers except for Internet Explorer and KaiOS. You can learn more in our [blog post ↗](https://blog.cloudflare.com/a-very-webp-new-year-from-cloudflare/).

The WebP version is served only when the `Accept` header from the browser includes WebP, and the WebP image is significantly smaller than the lossy or lossless recompression of the original format:

```
Accept: image/avif,image/webp,image/*,*/*;q=0.8
```

Polish only converts standard image formats _to_ the WebP format. If the origin server serves WebP images, Polish will not convert them, and will not optimize them.

#### File size, image quality, and WebP

Lossy formats like JPEG and WebP are able to generate files of any size, and every image could theoretically be made smaller. However, reduction in file size comes at a cost of reduction in image quality. Reduction of file sizes below each format's optimal size limit causes disproportionally large losses in quality. Re-encoding of files that are already optimized reduces their quality more than it reduces their file size.

Cloudflare will not convert from JPEG to WebP when the conversion would make the file bigger, or would reduce image quality by more than it would save in file size.

If you choose the Lossless Polish setting, then WebP will be used very rarely. This is due to the fact that, in this mode, WebP is only adequate for PNG images, and cannot improve compression for JPEG images.

Although WebP compresses better than JPEG on average, there are exceptions, and in some occasions JPEG compresses better than WebP. Cloudflare tries to detect these cases and keep the JPEG format.

If you serve low-quality JPEG images at the origin (quality setting 60 or lower), it may not be beneficial to convert them to WebP. This is because low-quality JPEG images have blocky edges and noise caused by compression, and these distortions increase file size of WebP images. We recommend serving high-quality JPEG images (quality setting between 80 and 90) at your origin server to avoid this issue.

If your server or Content Management System (CMS) has a built-in image converter or optimizer, it may interfere with Polish. It does not make sense to apply lossy optimizations twice to images, because quality degradation will be larger than the savings in file size.

## Polish interaction with Image optimization

Polish will not be applied to URLs using image transformations. Resized images already have lossy compression applied where possible, so they do not need the optimizations provided by Polish. Use the `format=auto` option to allow use of WebP and AVIF formats.

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/polish/compression/#page","headline":"Polish compression · Cloudflare Images docs","description":"Learn about Cloudflare's Polish compression options, including Lossless, Lossy, and WebP, to optimize image file sizes while managing metadata effectively.","url":"https://developers.cloudflare.com/images/polish/compression/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/polish/","name":"Cloudflare Polish"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/polish/compression/","name":"Polish compression"}}]}
```

---

---
title: WebP may be skipped
description: Cloudflare Polish skips WebP conversion when it would increase file size or degrade image quality.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# WebP may be skipped

Polish avoids converting images to the WebP format when such conversion would increase the file size, or significantly degrade image quality. Polish also optimizes JPEG images, and the WebP format is not always better than a well-optimized JPEG.

To enhance the use of WebP in Polish, enable the [Lossy option](https://developers.cloudflare.com/images/polish/compression/#lossy). When you create new JPEG images, save them with a slightly higher quality than usually necessary. We recommend JPEG quality settings between 85 and 95, but not higher. This gives Polish enough headroom for lossy conversion to WebP and optimized JPEG.

## In the **lossless** mode, it is not feasible to convert JPEG to WebP

WebP is actually a name for two quite different image formats: WebP-lossless (similar to PNG) and WebP-VP8 (similar to JPEG).

When the [Lossless option](https://developers.cloudflare.com/images/polish/compression/#lossless) is enabled, Polish will not perform any optimizations that change image pixels. This allows Polish to convert only between lossless image formats, such as PNG, GIF, and WebP-lossless. JPEG images will not be converted though, because the WebP-VP8 format does not support the conversion from JPEG without quality loss, and the WebP-lossless format does not compress images as heavily as JPEG.

In the lossless mode, Polish can still apply lossless optimizations to JPEG images. This is a unique feature of the JPEG format that does not have an equivalent in WebP.

## Low-quality JPEG images do not convert well to WebP

When JPEG files are already heavily compressed (for example, saved with a low quality setting like `q=50`, or re-saved many times), the conversion to WebP may not be beneficial, and may actually increase the file size. This is because lossy formats add distortions to images (for example, JPEG makes images blocky and adds noise around sharp edges), and the WebP format can not tell the difference between details of the image it needs to preserve and unwanted distortions caused by a previous compression. This forces WebP to wastefully use bytes on keeping the added noise and blockyness, which increases the file size, and makes compression less beneficial overall.

Polish never makes files larger. When we see that the conversion to WebP increases the file size, we skip it, and keep the smaller original file format.

## For some images conversion to WebP can degrade quality too much

The WebP format, in its more efficient VP8 mode, always loses some quality when compressing images. This means that the conversion from JPEG always makes WebP images look slightly worse. Polish ensures that file size savings from the conversion outweigh the quality loss.

Lossy WebP has a significant limitation: it can only keep one shade of color per 4 pixels. The color information is always stored at half of the image resolution. In high-resolution photos this degradation is rarely noticeable. However, in images with highly saturated colors and sharp edges, this limitation can result in the WebP format having noticeably pixelated or smudged edges.

Additionally, the WebP format applies smoothing to images. This feature hides blocky distortions that are a characteristic of low-quality JPEG images, but on the other hand it can cause loss of fine textures and details in high-quality images, making them look airbrushed.

Polish tries to avoid degrading images for too little gain. Polish keeps the JPEG format when it has about the same size as WebP, but better quality.

## Sometimes older formats are better than WebP

The WebP format has an advantage over JPEG when saving images with soft or blurry content, and when using low quality settings. WebP has fewer advantages when storing high-quality images with fine textures or noise. Polish applies optimizations to JPEG images too, and sometimes well-optimized JPEG is simply better than WebP, and gives a better quality and smaller file size at the same time. We try to detect these cases, and keep the JPEG format when it works better. Sometimes animations with little motion are more efficient as GIF than animated WebP.

The WebP format does not support progressive rendering. With [HTTP/2 prioritization](https://developers.cloudflare.com/speed/optimization/protocol/enhanced-http2-prioritization/) enabled, progressive JPEG images may appear to load quicker, even if their file sizes are larger.

## Beware of compression that is not better, only more of the same

With a lossy format like JPEG or WebP, it is always possible to take an existing image, save it with a slightly lower quality, and get an image that looks _almost_ the same, but has a smaller file size. It is the [heap paradox ↗](https://en.wikipedia.org/wiki/Sorites%5Fparadox): you can remove a grain of sand from a heap, and still have a heap of sand. There is no point when you can not make the heap smaller, except when there is no sand left. It is always possible to make an image with a slightly lower quality, all the way until all the accumulated losses degrade the image beyond recognition.

Avoid applying multiple lossy optimization tools to images, before or after Polish. Multiple lossy operations degrade quality disproportionally more than what they save in file sizes.

For this reason Polish will not create the smallest possible file sizes. Instead, Polish aims to maximize the quality to file size ratio, to create the smallest possible files while preserving good quality. The quality level we stop at is carefully chosen to minimize visual distortion, while still having a high compression ratio.

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/polish/no-webp/#page","headline":"WebP may be skipped · Cloudflare Images docs","description":"Cloudflare Polish skips WebP conversion when it would increase file size or degrade image quality.","url":"https://developers.cloudflare.com/images/polish/no-webp/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/polish/","name":"Cloudflare Polish"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/polish/no-webp/","name":"WebP may be skipped"}}]}
```

---

## List images

**get** `/accounts/{account_id}/images/v1`

List up to 100 images with one request. Use the optional parameters below to get a specific range of images.

### Path Parameters

- `account_id: string`

  Account identifier tag.

### Query Parameters

- `creator: optional string`

  Internal user ID set within the creator field. Setting to empty string "" will return images where creator field is not set

- `page: optional number`

  Page number of paginated results.

- `per_page: optional number`

  Number of items per page.

### Returns

- `errors: array of ResponseInfo`

  - `code: number`

  - `message: string`

  - `documentation_url: optional string`

  - `source: optional object { pointer }`

    - `pointer: optional string`

- `messages: array of ResponseInfo`

  - `code: number`

  - `message: string`

  - `documentation_url: optional string`

  - `source: optional object { pointer }`

- `result: object { images }`

  - `images: optional array of Image`

    - `id: optional string`

      Image unique identifier.

    - `creator: optional string`

      Can set the creator field with an internal user ID.

    - `filename: optional string`

      Image file name.

    - `meta: optional unknown`

      User modifiable key-value store. Can be used for keeping references to another system of record for managing images. Metadata must not exceed 1024 bytes.

    - `requireSignedURLs: optional boolean`

      Indicates whether the image can be a accessed only using it's UID. If set to true, a signed token needs to be generated with a signing key to view the image.

    - `uploaded: optional string`

      When the media item was uploaded.

    - `variants: optional array of string`

      Object specifying available variants for an image.

- `success: true`

  Whether the API call was successful

  - `true`

### Example

```http
curl https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/images/v1 \
    -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN"
```

#### Response

```json
{
  "errors": [
    {
      "code": 1000,
      "message": "message",
      "documentation_url": "documentation_url",
      "source": {
        "pointer": "pointer"
      }
    }
  ],
  "messages": [
    {
      "code": 1000,
      "message": "message",
      "documentation_url": "documentation_url",
      "source": {
        "pointer": "pointer"
      }
    }
  ],
  "result": {
    "images": [
      {
        "id": "id",
        "creator": "107b9558-dd06-4bbd-5fef-9c2c16bb7900",
        "filename": "logo.png",
        "meta": {
          "key": "value"
        },
        "requireSignedURLs": true,
        "uploaded": "2014-01-02T02:20:00.123Z",
        "variants": [
          "https://imagedelivery.net/MTt4OTd0b0w5aj/107b9558-dd06-4bbd-5fef-9c2c16bb7900/thumbnail",
          "https://imagedelivery.net/MTt4OTd0b0w5aj/107b9558-dd06-4bbd-5fef-9c2c16bb7900/hero",
          "https://imagedelivery.net/MTt4OTd0b0w5aj/107b9558-dd06-4bbd-5fef-9c2c16bb7900/original"
        ]
      }
    ]
  },
  "success": true
}
```

---

---
title: Examples
description: Code examples for common Cloudflare Images use cases, including transcoding and watermarking.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Examples

[Transcode imagesTranscode an image from Workers AI before uploading to R2](https://developers.cloudflare.com/images/examples/transcode-from-workers-ai/)[WatermarksDraw a watermark from KV on an image from R2](https://developers.cloudflare.com/images/examples/watermark-from-kv/)

```json
{"@context":"https://schema.org","@type":"WebPage","@id":"https://developers.cloudflare.com/images/examples/#page","headline":"Examples · Cloudflare Images docs","description":"Code examples for common Cloudflare Images use cases, including transcoding and watermarking.","url":"https://developers.cloudflare.com/images/examples/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/examples/","name":"Examples"}}]}
```

---

---
title: Transcode images
description: Transcode an image from Workers AI before uploading to R2
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Transcode images

Transcode an image from Workers AI before uploading to R2

JavaScript

```
const stream = await env.AI.run("@cf/bytedance/stable-diffusion-xl-lightning", {  prompt: YOUR_PROMPT_HERE,});
// Convert to AVIFconst image = (  await env.IMAGES.input(stream).output({ format: "image/avif" })).response();
const fileName = "image.avif";
// Upload to R2await env.R2.put(fileName, image.body);
```

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/examples/transcode-from-workers-ai/#page","headline":"Transcode images · Cloudflare Images docs","description":"Transcode an image from Workers AI before uploading to R2","url":"https://developers.cloudflare.com/images/examples/transcode-from-workers-ai/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/examples/","name":"Examples"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/examples/transcode-from-workers-ai/","name":"Transcode images"}}]}
```

---

---
title: Watermarks
description: Draw a watermark from KV on an image from R2
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Watermarks

Draw a watermark from KV on an image from R2

* [  JavaScript ](#tab-panel-9187)
* [  TypeScript ](#tab-panel-9188)

JavaScript

```
export default {  async fetch(request, env, ctx) {    const watermarkKey = "my-watermark";    const sourceKey = "my-source-image";
    const cache = await caches.open("transformed-images");    const cacheKey = new URL(sourceKey + "/" + watermarkKey, request.url);    const cacheResponse = await cache.match(cacheKey);
    if (cacheResponse) {      return cacheResponse;    }
    let watermark = await env.NAMESPACE.get(watermarkKey, "stream");    let source = await env.BUCKET.get(sourceKey);
    if (!watermark || !source) {      return new Response("Not found", { status: 404 });    }
    const result = await env.IMAGES.input(source.body)      .draw(watermark)      .output({ format: "image/jpeg" });
    const response = result.response();
    ctx.waitUntil(cache.put(cacheKey, response.clone()));
    return response;  },};
```

TypeScript

```
interface Env {  BUCKET: R2Bucket;  NAMESPACE: KVNamespace;  IMAGES: ImagesBinding;}export default {  async fetch(request, env, ctx): Promise<Response> {    const watermarkKey = "my-watermark";    const sourceKey = "my-source-image";
    const cache = await caches.open("transformed-images");    const cacheKey = new URL(sourceKey + "/" + watermarkKey, request.url);    const cacheResponse = await cache.match(cacheKey);
    if (cacheResponse) {      return cacheResponse;    }
    let watermark = await env.NAMESPACE.get(watermarkKey, "stream");    let source = await env.BUCKET.get(sourceKey);
    if (!watermark || !source) {      return new Response("Not found", { status: 404 });    }
    const result = await env.IMAGES.input(source.body)      .draw(watermark)      .output({ format: "image/jpeg" });
    const response = result.response();
    ctx.waitUntil(cache.put(cacheKey, response.clone()));
    return response;  },} satisfies ExportedHandler<Env>;
```

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/examples/watermark-from-kv/#page","headline":"Watermarks · Cloudflare Images docs","description":"Draw a watermark from KV on an image from R2","url":"https://developers.cloudflare.com/images/examples/watermark-from-kv/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/examples/","name":"Examples"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/examples/watermark-from-kv/","name":"Watermarks"}}]}
```

---

---
title: Introduction
description: Cloudflare Images provides a platform for optimizing, storing, and serving images at scale.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Introduction

Cloudflare provides a platform for building and scaling media applications with Images. On this page, we'll answer the following questions:

* Why optimize images?
* How do I get started with Images?
* Should I store with Images or R2?

---

## Why optimize images?

Loading images in their original resolution and format quickly becomes a bottleneck for app performance — especially on mobile.

Meanwhile, creating and storing multiple versions of the same image adds complexity and overhead, along with storage costs.

When you serve large amounts of media, image optimization provides:

* **Streamlined infrastructure** — Simplify your workflow and reduce infrastructure costs by dynamically generating optimized versions on request.
* **Smaller file sizes** — Automatically deliver images in modern formats like AVIF and WebP, which improves page speed and lowers bandwidth.
* **Responsive sizing** — Crop and resize for any use case, from square thumbnails to landscape banners, using the same original image in storage.
* **Visual effects** — Apply blur, overlays, background fills, and more at the edge.

## How do I get started with Images?

There are two ways to use Images, depending on where your images are stored:

### Optimize remote images

Keep your images on your own origin, in [R2](https://developers.cloudflare.com/r2), or with any storage provider. Cloudflare pulls the original image, applies optimizations at the edge, and caches the optimized image.

You can define an [origin allowlist](https://developers.cloudflare.com/images/optimization/transformations/sources/) to control which source images can be transformed on your zone.

To start, [enable transformations on your zone](https://developers.cloudflare.com/images/optimization/transformations/overview/).

### Upload and deliver with Images

Store, optimize, and deliver images globally with zero infrastructure management.

If your app centers around user-uploaded content, then you can use the [Direct Creator Upload API](https://developers.cloudflare.com/images/storage/upload-images/direct-creator-upload/) to securely accept images directly from your users.

To start, set up [predefined variants](https://developers.cloudflare.com/images/optimization/hosted-images/create-variants/) to configure how hosted images should be served.

## Should I store with Images or R2?

**Store in [R2](https://developers.cloudflare.com/r2/) and use Images for transformations** if you want to build your own custom image pipeline or need fine-grained control over storage, such as [bucket-level access management](https://developers.cloudflare.com/r2/buckets/) or [object lifecycle rules](https://developers.cloudflare.com/r2/buckets/object-lifecycles/). This is typically the most cost-effective approach for image optimization.

**Store in Images** if you want a fully managed solution with the least configuration. Our built-in features include a [shared delivery domain](https://developers.cloudflare.com/images/optimization/hosted-images/serve-uploaded-images/), [predefined variants](https://developers.cloudflare.com/images/optimization/hosted-images/create-variants/), and automatic cache invalidation when you update original images in storage.

Each use case has a separate pricing model. To learn more, refer to [Pricing](https://developers.cloudflare.com/images/pricing/).

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/get-started/introduction/#page","headline":"Introduction · Cloudflare Images docs","description":"Cloudflare Images provides a platform for optimizing, storing, and serving images at scale.","url":"https://developers.cloudflare.com/images/get-started/introduction/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/get-started/","name":"Get started"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/get-started/introduction/","name":"Introduction"}}]}
```

---

---
title: Key concepts
description: Definitions of core Cloudflare Images terms including transformations, variants, hosted images, and origins.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Key concepts

Here is a summary of the key terms that we use throughout our guides.

| Term               | What this means                                                                                                                                                                                                                                                                                                                                                                  |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Remote image       | An image that is stored outside of Images storage, including images in [R2](https://developers.cloudflare.com/r2/).                                                                                                                                                                                                                                                              |
| Transformation     | A request to optimize a remote image that is stored outside of Images.                                                                                                                                                                                                                                                                                                           |
| Origin             | The location where your image is stored.When you optimize a remote image, Cloudflare will pull the original image from the origin and store it in cache.                                                                                                                                                                                                                         |
| Hosted image       | An image that is stored in Images.Cloudflare dynamically serves copies of your original image, optimized based on your requirements.                                                                                                                                                                                                                                             |
| Parameter / Option | A parameter is a type of optimization that you can perform on an image.An option is the value for the parameter.For example, you can set the width parameter to a value of 100 to resize an image to a width of 100.                                                                                                                                                             |
| Variant            | A predefined way to specify how a hosted image should be resized.For example, you can create a variant called "thumbnail" that sets image dimensions to 100x100.When you serve images with this variant, Cloudflare will serve a version of the original image that is resized to 100x100.Predefined variants specify a limited set of parameters: width, height, fit, and blur. |

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/get-started/key-concepts/#page","headline":"Key concepts · Cloudflare Images docs","description":"Definitions of core Cloudflare Images terms including transformations, variants, hosted images, and origins.","url":"https://developers.cloudflare.com/images/get-started/key-concepts/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/get-started/","name":"Get started"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/get-started/key-concepts/","name":"Key concepts"}}]}
```

---

---
title: Limits and formats
description: Supported file formats, size limits, and dimension constraints for Cloudflare Images.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Limits and formats

This section covers limits and supported formats for Images.

---

## Limits

Here are limits to keep in mind when optimizing with Images.

On an Enterprise plan, you can reach out to our account team to ensure that the limits align with your needs.

### Remote images

The following limits apply when transforming a remote image stored outside of Images:

| Attribute                                | Limit                              |
| ---------------------------------------- | ---------------------------------- |
| Image file size                          | 100 MB                             |
| Image area, excluding animated GIFs      | 100 MP (e.g. 10,000x10,000 pixels) |
| Image area for animated GIFs             | 100 MP\*                           |
| Image dimension, excluding WebP and AVIF | 12,000 pixels                      |
| Image dimension, AVIF                    | 1,200 pixels                       |

### Hosted images

The following limits apply when uploading to your Images storage:

| Attribute                                | Limit                              |
| ---------------------------------------- | ---------------------------------- |
| Image file size                          | 10 MB                              |
| Image area, excluding animated GIFs      | 100 MP (e.g. 10,000x10,000 pixels) |
| Image area, animated GIFs                | 100 MP\*                           |
| Image dimension, excluding WebP and AVIF | 12,000 pixels                      |
| Image dimension, AVIF                    | 1,200 pixels                       |
| Image metadata                           | 1024 bytes                         |

### Limits for animated images

GIF/WebP animations are limited to the total megapixels across all frames, or the sum of areas of all frames. For example, a GIF with 500x500 dimensions and 10 frames has an image area of 2,500,000 pixels or 2.5 megapixels.

The limit to deliver an animated GIF/WebP animation is 100 megapixels. However, any animations over 50 megapixels will be delivered without applying any transformations.

When serving animations, we recommend using video formats like MP4 and WebM for best performance. As the GIF format has inefficient compression, high resolution animations typically have larger file sizes and take longer to compress.

To optimize remote videos, you can use [media transformations ↗](https://developers.cloudflare.com/stream/transform-videos/).

### Limits for the Images binding

When optimizing with the [Images binding](https://developers.cloudflare.com/images/optimization/binding/), the maximum input size for `.input()` is 20 MB.

## Supported formats

### Input formats

Images supports a wide range of input formats for both remote and hosted images:

* PNG
* JPEG
* GIF (including animations)
* WebP (including animations)
* SVG
* AVIF\*
* HEIC

\*Available on an Enterprise plan.

### Output formats

You can serve images in the following output formats:

* PNG
* JPEG
* GIF (including animations)
* WebP (including animations)
* SVG
* AVIF

When detecting the most optimal output format for the requesting browser, Cloudflare balances the time to generate an image with the time to serve the image to the browser.

In particular, AVIF encoding can be an order of magnitude slower than encoding to other formats. If the image is too large to be quickly encoded to AVIF, then Cloudflare will fall back to WebP or JPEG.

### Progressive JPEG

When transcoding to JPEG, Cloudflare generates images in an interlaced progressive JPEG format.

You can use the [format](https://developers.cloudflare.com/images/optimization/features/#format) parameter to specify whether progressive or baseline JPEG should be used.

However, we will always fall back to the baseline JPEG format — even when progressive JPEG is specified — if either:

* The output image area dimensions are less than 50x50.
* The output image area dimensions are greater than 3000x3000.

### SVG

Cloudflare does not resize SVG files and will ignore any optimization parameters.

If you store in Images, then you can use any predefined variant as a placeholder to deliver a sanitized SVG. For example, applying the default public variant allows the SVG to be delivered without resizing or cropping:

`imagedelivery.net/account_hash/svg_id/public`

Similarly, you can use Images to serve a sanitized SVG that is stored in your own origin, like in [R2](https://developers.cloudflare.com/r2/).

When SVG files are served, they are sanitized using [svg-hush ↗](https://github.com/cloudflare/svg-hush), an open-source tool developed by Cloudflare to make SVGs as safe as possible. It streams the files without buffering, enabling us to quickly filter them on the fly. SVG files are XML documents and can contain links or Javascript features that may pose a security concern.

The `svg-hush` tool filters SVGs and removes potentially risky features, such as:

* **Scripting.** We prevent SVGs from being used for cross-site scripting attacks. Although browsers do not allow scripts in `<img>` tags, they do allow scripting when SVGs are opened directly as a top-level document.
* **Hyperlinks to other documents.** Removing hyperlinking makes SVG files less attractive for SEO spam and phishing.
* **References to cross-origin resources.** We stop third parties from tracking who is viewing the image.

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/get-started/limits/#page","headline":"Limits and formats · Cloudflare Images docs","description":"Supported file formats, size limits, and dimension constraints for Cloudflare Images.","url":"https://developers.cloudflare.com/images/get-started/limits/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-06-16","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/get-started/","name":"Get started"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/get-started/limits/","name":"Limits and formats"}}]}
```

---

---
title: Optimize with Workers
description: Use the Images binding to optimize, resize, and manipulate images directly in a Worker from any source.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Optimize with Workers

A [binding](https://developers.cloudflare.com/workers/runtime-apis/bindings/) connects your [Worker](https://developers.cloudflare.com/workers/) to external resources on the Developer Platform, like [Images](https://developers.cloudflare.com/images/), [R2 buckets](https://developers.cloudflare.com/r2/buckets/), or [KV namespaces](https://developers.cloudflare.com/kv/concepts/kv-namespaces/).

The Images binding lets you optimize and manipulate images directly in a Worker. Unlike the [URL interface](https://developers.cloudflare.com/images/optimization/transformations/overview/), which requires images to be accessible through a URL, the binding works with raw image bytes. You can pass images from any source, including [Images](https://developers.cloudflare.com/images/storage/upload-images/methods/), [R2](https://developers.cloudflare.com/r2/), a `fetch()` response, or a request body.

With the Images binding, you can:

* Optimize an image stored in Images or R2 by passing the bytes directly, instead of fetching through a public URL.
* Resize an image, overlay a watermark, then resize the combined output into a final result — all in a single chain of operations.
* Control the order of operations for optimization parameters. When you use the URL interface, the optimization parameters follow a fixed order of operations.

Bindings can be configured in the Cloudflare dashboard for your Worker or in the Wrangler configuration file in your project's directory.

Billing

Every call to the Images binding counts as one unique transformation. Refer to [Images pricing](https://developers.cloudflare.com/images/pricing/) for more information about billing.

## Setup

The Images binding is enabled on a per-Worker basis.

You can define variables in the Wrangler configuration file of your Worker project's directory. These variables are bound to external resources at runtime, and you can then interact with them through this variable.

To bind Images to your Worker, add the following to the end of your Wrangler configuration file:

* [  wrangler.jsonc ](#tab-panel-9189)
* [  wrangler.toml ](#tab-panel-9190)

JSONC

```
{  "images": {    "binding": "IMAGES", // i.e. available in your Worker on env.IMAGES  },}
```

TOML

```
[images]binding = "IMAGES"
```

Within your Worker code, use `env.IMAGES.input()` to build an object that can manipulate the image (passed as a `ReadableStream`).

## Methods

Note

Responses from the Images binding are not automatically cached. Workers lets you interact directly with the [Cache API](https://developers.cloudflare.com/workers/runtime-apis/cache/) to customize cache behavior. You can implement logic in your script to store transformations in Cloudflare's cache.

### `.input(stream)`

Creates an optimization handle for an image. All operations begin with this method. Accepts image bytes up to 20 MB from any source, including [Images](https://developers.cloudflare.com/images/storage/upload-images/methods/), [R2](https://developers.cloudflare.com/r2/), a `fetch()` response, or a request body.

Returns a handle that you can use to chain `.transform()`, `.draw()`, and `.output()` calls.

* [  JavaScript ](#tab-panel-9195)
* [  TypeScript ](#tab-panel-9196)

JavaScript

```
export default {  async fetch(request, env) {    const imageURL = "https://example.com/photo.jpg";
    const response = await fetch(imageURL);
    return (      await env.IMAGES.input(response.body)        .transform({ width: 800 })        .output({ format: "image/webp" })    ).response();  },};
```

TypeScript

```
export default {  async fetch(request, env) {    const imageURL = "https://example.com/photo.jpg";
    const response = await fetch(imageURL);
    return (      await env.IMAGES        .input(response.body)        .transform({ width: 800 })        .output({ format: "image/webp" })    ).response();  },};
```

### `.transform(options)`

Applies optimization parameters to the image, such as `width`, `height`, or `blur`. You can chain multiple `.transform()` calls to control the order that parameters will be applied.

For the full list of parameters, refer to [Features](https://developers.cloudflare.com/images/optimization/features/).

The example below shows how you can resize an image that is [stored in Images](https://developers.cloudflare.com/images/storage/binding/) by getting the image bytes:

* [  JavaScript ](#tab-panel-9191)
* [  TypeScript ](#tab-panel-9192)

JavaScript

```
// Get the raw bytes of a hosted imageconst bytes = await env.IMAGES.hosted.image("IMAGE_ID").bytes();
// Resize and transcode the imageconst response = (  await env.IMAGES.input(bytes)    .transform({ width: 400 })    .output({ format: "image/webp" })).response();
return response;
```

TypeScript

```
// Get the raw bytes of a hosted imageconst bytes = await env.IMAGES.hosted.image("IMAGE_ID").bytes();
// Resize and transcode the imageconst response = (  await env.IMAGES.input(bytes)    .transform({ width: 400 })    .output({ format: "image/webp" })).response();
return response;
```

### `.draw(image, options)`

Draws an overlay image over another image.

The overlay can be a stream of image bytes or another `.input()` chain. You can pass a child `.transform()` function inside this method to resize or manipulate the overlay before drawing it.

Accepts `opacity`, `repeat`, a side (`left`, `right`, `top`, `bottom`), and `composite`. For the full list of draw options and examples, refer to [Draw overlays and watermarks](https://developers.cloudflare.com/images/optimization/draw-overlays/#options).

### `.output(options)`

Generates the final image with the specified output options.

Accepts the following options:

* `format` — Encodes the image in [a supported format](https://developers.cloudflare.com/images/get-started/limits/#output-formats), such as AVIF, WebP, or JPEG. This method is required — there is no default output format.
* `quality` — Specifies the output [quality](https://developers.cloudflare.com/images/optimization/features/#quality--q) of an image for JPEG, WebP, and AVIF formats, expressed as a fixed value or perceptual quality level.
* `anim` — Specifies whether to [preserve animation frames](https://developers.cloudflare.com/images/optimization/features/#anim) from input files. Set `anim:false` to convert animations to still images.

* [  JavaScript ](#tab-panel-9193)
* [  TypeScript ](#tab-panel-9194)

JavaScript

```
const response = (  await env.IMAGES.input(stream)    .transform({ rotate: 90 })    .transform({ width: 128 })    .transform({ blur: 20 })    .output({ format: "image/avif" })).response();
return response;
```

TypeScript

```
const response = (  await env.IMAGES.input(stream)    .transform({ rotate: 90 })    .transform({ width: 128 })    .transform({ blur: 20 })    .output({ format: "image/avif" })).response();
return response;
```

### `.info(stream)`

Outputs information about the image, such as `format`, `fileSize`, `width`, and `height`.

## Interact with your Images binding locally

The Images API can be used in local development through [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/), the command-line interface for Workers. Using the Images binding in local development will not incur usage charges.

Wrangler supports two different versions of the Images API:

* A high-fidelity version that supports all features that are available through the Images API. This is the same version that Cloudflare runs globally in production.
* A low-fidelity offline version that supports only a subset of features, such as resizing and rotation.

To test the low-fidelity version of Images, you can run `wrangler dev`:

```
npx wrangler dev
```

Currently, this version supports only `width`, `height`, `rotate`, and `format`.

To test the high-fidelity remote version of Images, you can use the `--remote` flag:

```
npx wrangler dev --remote
```

When testing with the [Workers Vitest integration](https://developers.cloudflare.com/workers/testing/vitest-integration/), the low-fidelity offline version is used by default, to avoid hitting the Cloudflare API in tests.

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/optimization/binding/#page","headline":"Optimize with Workers · Cloudflare Images docs","description":"Use the Images binding to optimize, resize, and manipulate images directly in a Worker from any source.","url":"https://developers.cloudflare.com/images/optimization/binding/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-06-16","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/optimization/","name":"Optimization"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/optimization/binding/","name":"Optimize with Workers"}}]}
```

---

---
title: Draw overlays and watermarks
description: Add watermarks, logos, and overlay images over other images using Workers.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Draw overlays and watermarks

Use [Workers](https://developers.cloudflare.com/workers/) to draw watermarks, logos, signatures over other images. Overlays support transparency, positioning, and compositing modes.

You can draw overlays in a Worker using two approaches:

* **[cf.image on a fetch subrequest](#draw-with-cfimage)** — Add a `draw` array to the image options. All images must be accessible via URL. Use this approach when optimizing images through the [URL interface](https://developers.cloudflare.com/images/optimization/features/#url-interface).
* **[Images binding](#draw-with-the-images-binding)** — Chain `.draw()` calls when overlaying images from any source, including [hosted images](https://developers.cloudflare.com/images/storage/binding/) or [R2](https://developers.cloudflare.com/r2/).

## Draw with `cf.image`

To draw overlays on a [fetch() subrequest](https://developers.cloudflare.com/images/optimization/transformations/transform-via-workers/) in Workers, you can add a `draw` array to your `cf.image` options.

Each entry in the array specifies an overlay and its options, including [optimization parameters](https://developers.cloudflare.com/images/optimization/features/) like [width](https://developers.cloudflare.com/images/optimization/features/#width--w), [height](https://developers.cloudflare.com/images/optimization/features/#height--h), [fit](https://developers.cloudflare.com/images/optimization/features/#fit), [blur](https://developers.cloudflare.com/images/optimization/features/#blur), and [rotate](https://developers.cloudflare.com/images/optimization/features/#rotate). Overlays are drawn in the order they appear — the last entry is the topmost layer.

JavaScript

```
export default {  async fetch(request) {    const imageURL = "https://example.com/image.png";
    return fetch(imageURL, {      cf: {        image: {          width: 800,          height: 600,          draw: [            {              url: "https://example.com/branding/logo.png",              bottom: 5,              right: 5,              fit: "contain",              width: 100,              height: 50,              opacity: 0.8,            },          ],        },      },    });  },};
```

## Draw with the Images binding

The [Images binding](https://developers.cloudflare.com/images/optimization/binding/) uses a chainable `.draw()` method to draw an overlay on top of an image. You can chain multiple `.draw()` calls for multiple overlays.

Pass the overlay image as the first argument, then the draw options as the second. To apply [optimization parameters](https://developers.cloudflare.com/images/optimization/features/) to the overlay image, pass an `.input()` chain with `.transform()` as the first argument.

JavaScript

```
export default {  async fetch(request, env) {    const img = await fetch("https://zzzdna.com/blue.png");    const watermark = await fetch("https://zzzdna.com/purple.png");
    const response = (      await env.IMAGES        .input(img.body)        .draw(          env.IMAGES.input(watermark.body).transform({ width: 100 }),          { bottom: 10, right: 10, opacity: 0.5 }        )        .output({ format: "image/avif" })    ).response();
    return response;  },};
```

## Options

The dimensions of the output image are always determined by the base image. The overlay is then drawn onto the base image's canvas.

The following draw-specific options can be used for positioning and blending.

### `url`

Absolute URL of the overlay image when drawing with `cf.image`. Supports any [supported image format](https://developers.cloudflare.com/images/get-started/limits/). For watermarks or non-rectangular overlays, use PNG or WebP images.

When drawing with the Images binding, the overlay is passed as image bytes or an `.input()` chain instead of a URL. Refer to [Draw with the Images binding](#draw-with-the-images-binding).

### `width` and `height`

Sets the maximum dimensions of the overlay image when drawing with `cf.image`. Accepts an integer (pixels) or a decimal between `0` and `1` representing a fraction of the base image's dimension. For example, `height:0.25` sets the overlay height to 25% of the height of the base image.

Use [fit](https://developers.cloudflare.com/images/optimization/features/#fit) and [gravity](https://developers.cloudflare.com/images/optimization/features/#gravity--g) to control how the overlay image is resized and cropped.

When drawing with the Images binding, the dimensions of the overlay image can be set using the `.transform()` method.

### `repeat`

Determines whether to tile the overlay across the image.

Accepts the following values:

* `true` — Tiles the overlay to cover the entire area. This is useful for watermarks.
* `x` — Tiles the overlay horizontally only.
* `y` — Tiles the overlay vertically only.

### `top`, `left`, `bottom`, `right`

Sets the position of the overlay image as an offset, in pixels, to the specified edge. `0` aligns the overlay flush to the edge. If no position is specified, then the overlay is centered.

For example, `{ bottom: 0, right: 10 }` places the overlay at the bottom-right corner, 10 pixels inward from the right edge.

Setting both `left` and `right`, or both `top` and `bottom` returns an error.

### `opacity`

Sets the opacity of the overlay image. Accepts a decimal value between `0.0` (fully transparent) and `1.0` (fully opaque). For example, `opacity: 0.5` makes the overlay semitransparent.

### `composite`

Controls how the overlay is blended with the base image using [Porter-Duff compositing operations ↗](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/feComposite). The default is `over`.

The composite mode only affects the area within the overlay's bounding box. The base image is always preserved outside of this area.

Accepts the following values:

#### `over`

Draws the overlay image on top of the base image. This is the default `composite` behavior.

The overlay covers the base where they overlap. Both images are visible where they do not overlap.

![composite=over output](https://developers.cloudflare.com/_astro/over.CJQByGnz.png) 

#### `in`

Shows the overlay only where the base image is opaque. If the overlay has transparent pixels that overlap with the base image, then those areas of the base image will also become transparent.

![composite=in output](https://developers.cloudflare.com/_astro/in.BaGoUETE.png) 

#### `atop`

Draws the overlay image on top of the base image, but only where the base image is opaque. This will clip the overlay image to the shape of the base image.

If the overlay has transparent pixels that overlap with the base image, then the base image remains visible (unlike `in`).

![composite=atop output](https://developers.cloudflare.com/_astro/atop.qvrwEw9r.png) 

#### `out`

Shows the overlay only where the base image is transparent. Within the overlay's bounding box, opaque areas of the base image will become transparent.

![composite=out output](https://developers.cloudflare.com/_astro/out.BU8dQJ0s.png) 

#### `xor`

Shows the areas of each image where the other is transparent. Overlapping opaque areas will become transparent. This can be used to create [rounded corners](https://developers.cloudflare.com/images/optimization/draw-overlays/#rounded-corners) or custom shapes.

![composite=xor output](https://developers.cloudflare.com/_astro/xor.6hxgorNh.png) 

#### `lighter`

Adds the color values of both images, which makes the overlapping areas brighter.

![composite=lighter output](https://developers.cloudflare.com/_astro/lighter.V0ahkzW6.png) 

## Examples

### Watermark

Tile a semitransparent watermark across the entire image using `cf.image`.

JavaScript

```
fetch(imageURL, {  cf: {    image: {      draw: [        {          url: "https://example.com/watermark.png",          repeat: true,          opacity: 0.2,        },      ],    },  },});
```

### Logo in the corner

Position a logo at the bottom-right corner using `cf.image`.

JavaScript

```
fetch(imageURL, {  cf: {    image: {      draw: [        {          url: "https://example.com/logo.png",          bottom: 5,          right: 5,        },      ],    },  },});
```

### Multiple overlays

Combine multiple overlays in one request using `cf.image`. They are drawn in order — the last entry is the topmost layer.

JavaScript

```
fetch(imageURL, {  cf: {    image: {      draw: [        { url: "https://example.com/watermark.png", repeat: true, opacity: 0.2 },        { url: "https://example.com/play-button.png" },        { url: "https://example.com/logo.png", bottom: 5, right: 5 },      ],    },  },});
```

### Rounded corners

Cut rounded corners from an image. A corner mask is drawn at each corner and rotated to match the position. `xor` removes the overlapping pixels.

The example below shows how this can be done using the [Images binding](https://developers.cloudflare.com/images/optimization/binding/).

TypeScript

```
const image = await fetch("https://example.com/photo.png");const mask = await fetch("https://example.com/corner-mask.png");
let [topLeft, topRight] = mask.body.tee();let bottomLeft, bottomRight;[topLeft, bottomLeft] = topLeft.tee();[topLeft, bottomRight] = topLeft.tee();
const output = await env.IMAGES  .input(image.body)  .draw(env.IMAGES.input(topLeft).transform({ rotate: 0 }), {    left: 0,    top: 0,    composite: "xor",  })  .draw(env.IMAGES.input(topRight).transform({ rotate: 90 }), {    right: 0,    top: 0,    composite: "xor",  })  .draw(env.IMAGES.input(bottomRight).transform({ rotate: 180 }), {    bottom: 0,    right: 0,    composite: "xor",  })  .draw(env.IMAGES.input(bottomLeft).transform({ rotate: 270 }), {    bottom: 0,    left: 0,    composite: "xor",  })  .output({ format: "image/png" });
return output.response();
```

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/optimization/draw-overlays/#page","headline":"Draw overlays and watermarks · Cloudflare Images docs","description":"Add watermarks, logos, and overlay images over other images using Workers.","url":"https://developers.cloudflare.com/images/optimization/draw-overlays/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-06-16","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/optimization/","name":"Optimization"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/optimization/draw-overlays/","name":"Draw overlays and watermarks"}}]}
```

---

---
title: Features
description: Available Cloudflare Images optimization parameters for resizing, cropping, format conversion, and visual effects.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Features

Cloudflare enables developers to optimize images at scale by dynamically generating different versions in real time.

The guide describes all of the parameters that can be used to resize, crop, manipulate, and apply visual effects to images.

## How to apply optimization

Use Cloudflare's image optimization capabilities through:

* **URL interface** — Apply parameters directly in the image URL to specify how images should be optimized when served to the browser.
* **Workers** — Bind the Images API directly to your Worker or set the `cf.image` options on a `fetch` subrequest to build programmatic image workflows.

### URL interface

Cloudflare uses a different URL structure depending on whether you are optimizing a [remote](https://developers.cloudflare.com/images/optimization/transformations/overview/) or a [hosted](https://developers.cloudflare.com/images/optimization/hosted-images/serve-uploaded-images/) image:

* [ Remote image (transformation) ](#tab-panel-9248)
* [ Hosted image ](#tab-panel-9249)

When optimizing images outside of Images, the default transformation URL uses the following structure:

```
https://<ZONE>/cdn-cgi/image/<OPTIONS>/<SOURCE-IMAGE>
```

URL breakdown

| Part            | Description                                                                                                                                                                                                                                                    |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <ZONE>          | Your domain name at Cloudflare. Transformations can be requested on every Cloudflare zone that has transformations enabled.                                                                                                                                    |
| /cdn-cgi/image/ | A fixed prefix that identifies that this path is a request to optimize an image. To hide this part, you can set up [Transform Rules](https://developers.cloudflare.com/images/optimization/transformations/rewrite-rules/) to serve images from a custom path. |
| <OPTIONS>       | A list of optimization parameters, separated by a comma. A valid URL must specify at least one parameter.                                                                                                                                                      |
| <SOURCE-IMAGE>  | The original image that you want to transform. You can use an absolute path on the origin server or an absolute URL (that starts with https:// or http://).                                                                                                    |

For images stored in Cloudflare Images, use the delivery URL with a variant or custom options:

```
https://imagedelivery.net/<ACCOUNT_HASH>/<IMAGE-ID>/<VARIANT-OR-OPTIONS>
```

URL breakdown

| Part                 | Description                                                                                                                                                                                                                                                             |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| imagedelivery.net    | A shared, Cloudflare-owned domain for optimizing images that are hosted in Images. As an alternative, you can also [serve images from your own domain](https://developers.cloudflare.com/images/optimization/hosted-images/serve-from-custom-domains/).                 |
| <ACCOUNT\_HASH>      | A unique identifier for your Cloudflare account. You can find your account hash in the [Cloudflare dashboard ↗](https://dash.cloudflare.com/?to=/:account/images/hosted) under **Images** \> **Developer Resources**.                                                   |
| <IMAGE-ID>           | The unique identifier for a hosted image. When you upload to Images, Cloudflare automatically generates an image ID. You can also set a [custom ID](https://developers.cloudflare.com/images/storage/upload-images/upload-custom-path/) to use your own path structure. |
| <VARIANT-OR-OPTIONS> | Here, you can specify a [predefined variant](https://developers.cloudflare.com/images/optimization/hosted-images/create-variants/) or a list of optimization parameters, separated by a comma. A valid URL must specify either a variant or at least one parameter.     |

### Workers

When using [Images with Workers](https://developers.cloudflare.com/images/optimization/transformations/transform-via-workers/), you can:

* Apply custom logic to set the order for optimization operations. For example, by default, Images will apply `flip` before `rotate`; instead, you can use the Images binding to customize your optimization workflow to rotate the image before flipping it.
* Use a custom URL scheme instead of the default URL structure.
* Implement content negotiation to dynamically adapt image size, format, and quality based on the device and network condition.

---

## Parameters

### `anim`

Specifies whether to preserve animation frames from input files.

* `true` (default) — Outputs the animated image with all frames.
* `false` — Converts the first frame of an animated input to a still image.

This setting is recommended when enlarging images or processing arbitrary user-uploaded content, as animated GIFs can have large file sizes and increase page load times. When using `format=json`, it is also useful to set `anim=false` to get a quicker response without the number of frames.

| ![Original animation](https://developers.cloudflare.com/_astro/anim.B4kULVAW.gif) | ![anim=false output](https://developers.cloudflare.com/_astro/anim.Cxswk-aF.png) |
| --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| **Original**                                                                      | anim=false                                                                       |

* [ URL format ](#tab-panel-9200)
* [ Workers ](#tab-panel-9201)

```
anim=false
```

JavaScript

```
cf: {image: {anim: false}}
```

### `background`

Specifies an opaque or transparent color to fill blank or transparent pixels in the image. The default is `%23FFFFFF` (white).

Accepts the following properties:

* A HEX color code, formatted as `%23RRGGBB`.
* A CSS color name, e.g. `white` or `red`.
* An `rgb()` or `rgba()` CSS color function, e.g. `rgba(250,40,145,0.5)`.

The background color is visible in images with transparent pixels, including images that are resized with `fit=pad`.

| ![Original image](https://developers.cloudflare.com/_astro/original.DuemPfHh.jpg) | ![background=red output](https://developers.cloudflare.com/_astro/background-red.BDIBWvns.jpg) |
| --------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| **Original**1080 x 720                                                            | **Output**1080 x 900                                                                           |

* [ URL format ](#tab-panel-9202)
* [ Workers ](#tab-panel-9203)

```
background=%23ff0000background=redbackground=rgb%28240%2C40%2C145%29
```

JavaScript

```
cf: {image: {background: "#RRGGBB"}}cf: {image: {background: "rgba(240,40,145,0)"}}
```

### `blur`

Applies a blur radius to the image. Accepts an integer from `0` (no blur) to `250` (maximum blur). The default is `0`.

This parameter should not be used to reliably obscure image content when optimizing via URL, as the URL can be modified to remove the blur parameter. Instead, you can [restrict access to the original image](https://developers.cloudflare.com/images/optimization/transformations/transform-via-workers/) through Workers.

| ![Original image](https://developers.cloudflare.com/_astro/original.DuemPfHh.jpg) | ![blur=50 output](https://developers.cloudflare.com/_astro/blur-50.CHVCNtdi.jpg) |
| --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| **Original**                                                                      | blur=50                                                                          |

* [ URL format ](#tab-panel-9204)
* [ Workers ](#tab-panel-9205)

```
blur=50
```

JavaScript

```
cf: {image: {blur: 50}}
```

### `border`

Adds a border around the image.

Note

This feature is available only in Workers.

Accepts the following properties:

* `color` — Sets the color of the border. Accepts any valid CSS color value, for example `#FF0000`, `rgb(0,0,0)`, or `red`.
* `width` — Sets the uniform border, in pixels, on all four sides.
* `top`, `right`, `bottom`, `left` — Sets the border width, in pixels, for individual sides.

The border is applied after the image has been resized. The border width automatically scales with the [dpr](https://developers.cloudflare.com/images/optimization/features#dpr) parameter to ensure sharpness on high-resolution screens.

* [ Workers ](#tab-panel-9197)

JavaScript

```
cf: {image: {border: {color: "rgb(0,0,0,0)", top: 5, right: 10, bottom: 5, left: 10}}}cf: {image: {border: {color: "#FFFFFF", width: 10}}}
```

### `brightness`

Adjusts the image's overall luminance using a multiplier.

* `1` (default) — No change to the original brightness.
* `< 1.0` — Darkens the image, e.g. `0.5` is half as bright.
* `> 1.0` — Lightens the image, e.g. `2` is twice as bright.

| ![Original image](https://developers.cloudflare.com/_astro/original.DuemPfHh.jpg) | ![brightness=0.5 output](https://developers.cloudflare.com/_astro/brightness-0.5.D3jxMNxf.jpg) | ![brightness=2 output](https://developers.cloudflare.com/_astro/brightness-2.D-IbGyod.jpg) |
| --------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| **Original**                                                                      | brightness=0.5                                                                                 | brightness=2                                                                               |

* [ URL format ](#tab-panel-9206)
* [ Workers ](#tab-panel-9207)

```
brightness=0.5
```

JavaScript

```
cf: {image: {brightness: 0.5}}
```

### `compression`

Selects the output format that is quickest to compress. Accepts `fast`. The default is none.

The `compression=fast` option prioritizes encoding speed over output quality and file size, and will usually override the `format` parameter to choose JPEG over more efficient formats like AVIF or WebP. This slightly reduces latency on a cache miss, but may result in increased file size and lower image quality.

This option is not recommended, except in unusual circumstances like resizing uncacheable, dynamically-generated images.

* [ URL format ](#tab-panel-9208)
* [ Workers ](#tab-panel-9209)

```
compression=fast
```

JavaScript

```
cf: {image: {compression: "fast"}}
```

### `contrast`

Adjusts the image's overall difference between the darkest and lightest parts using a multiplier.

* `1` (default) — No change to the original contrast.
* `< 1.0` — Decreases contrast, which makes shadows lighter and highlights darker.
* `> 1.0` — Increases contrast, which pushes shadows toward black and highlights toward white.

| ![Original image](https://developers.cloudflare.com/_astro/original.DuemPfHh.jpg) | ![contrast=0.5 output](https://developers.cloudflare.com/_astro/contrast-0.5.BJSw_Q0N.jpg) | ![contrast=2 output](https://developers.cloudflare.com/_astro/contrast-2.yLjBBcQQ.jpg) |
| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- |
| **Original**                                                                      | contrast=0.5                                                                               | contrast=2                                                                             |

* [ URL format ](#tab-panel-9210)
* [ Workers ](#tab-panel-9211)

```
contrast=0.5
```

JavaScript

```
cf: {image: {contrast: 0.5}}
```

### `dpr`

Scales the output resolution by a multiplier to match a user's specific screen density (for example, Retina or 4K). The default is `1`, which delivers the image at the exact width and height requested. The maximum supported value is `2`.

Modern devices have more physical pixels than CSS pixels. If you serve a 300px image in a 300px container on a high-DPR smartphone, then it will look blurry. Using `dpr=2` tells Cloudflare to send a 600px image for the same 300px container, which results in a clearer, crisper image.

The `dpr` parameter can be used with `srcset` to [serve responsive images](https://developers.cloudflare.com/images/optimization/make-responsive-images/).

| ![dpr=1 output](https://developers.cloudflare.com/_astro/dpr-1.kw44tjdd.jpg) | ![dpr=2 output](https://developers.cloudflare.com/_astro/dpr-2.frEHI63e.jpg) |
| ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| width=300,height=200,dpr=1                                                   | width=300,height=200,dpr=2                                                   |

* [ URL format ](#tab-panel-9212)
* [ Workers ](#tab-panel-9213)

```
dpr=1
```

JavaScript

```
cf: {image: {dpr: 1}}
```

### `fit`

Specifies how the image is fit to the target area.

Fit is performed after setting the [width](#width) and [height](#height) dimensions of the image.

| Option               | Result                                                        | Match original aspect ratio | Upscales |
| -------------------- | ------------------------------------------------------------- | --------------------------- | -------- |
| scale-down (default) | Show entire image without cropping or upscaling               | Yes                         | No       |
| contain              | Show entire image without cropping                            | Yes                         | Yes      |
| cover                | Fill the entire requested area, cropping if needed            | No                          | Yes      |
| crop                 | Fill the entire requested area, but never upscales            | No                          | No       |
| aspect-crop          | Crop to match the target aspect ratio, but never upscales     | No                          | No       |
| pad                  | Fit within the target area, adding space for remaining area   | Yes                         | Yes      |
| squeeze              | Scale to exact dimensions, distorting if needed               | No                          | Yes      |
| scale-up             | Upscales while showing the entire image, but never downscales | Yes                         | Yes      |

* [ URL format ](#tab-panel-9214)
* [ Workers ](#tab-panel-9215)

```
fit=pad
```

JavaScript

```
cf: {image: {fit: "pad"}}
```

#### `scale-down`

Resizes the image to fit within the specified dimensions while preserving its original aspect ratio, but never upscales the image. This is the default `fit` behavior.

When the original image is smaller than the target area, it is returned at its original dimensions. For example, a request to serve a 1080x720 image at 2000x2000 will return the image at 1080x720.

When larger, it downscales the image to fit the target area while matching the original aspect ratio.

In the example below, the 1080x720 image is resized to fit within the target 500x500 area. Since `scale-down` preserves the original aspect ratio (3:2), the final dimensions of the output image are 500x333.

| ![original image](https://developers.cloudflare.com/_astro/pete-landscape.C1O-FBM1.jpg) | ![target area](https://developers.cloudflare.com/_astro/1296x1296.BYEj6EvB.png) | ![fit=scale-down output](https://developers.cloudflare.com/_astro/pete-contain.B_qfOwMN.png) |
| --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| **Original**1080 x 720 (3:2)                                                            | **Requested**500 x 500 (1:1)                                                    | **Output**500 x 333 (3:2)                                                                    |

#### `contain`

Resizes the image to be as large as possible within the target `width` and `height` dimensions while preserving its original aspect ratio.

When the original image is larger than the target area, it downscales to fit the target area (like `scale-down`).

When smaller, it upscales instead (like `scale-up`). Works with the [upscale](#upscale) parameter to control the algorithm for enlarging an image. To avoid upscaling, use `scale-down`.

#### `cover`

Fills the entire target area, shrinking or enlarging the image if needed. The output area always matches the requested `width` and `height` dimensions exactly.

When the original and target aspect ratios differ, the image is resized to cover the full target area and any overflow is cropped. Use the [gravity](#gravity) parameter to control which part of the image is preserved during cropping.

Works with the [upscale](#upscale) parameter to control the algorithm for enlarging an image.

In the example below, the 1080×720 image is first resized to 750×500 (matching the requested height) to fit the target area, then cropped from the left and right edges to its final 500x500 dimensions.

| ![original image](https://developers.cloudflare.com/_astro/pete-landscape.C1O-FBM1.jpg) | ![target area](https://developers.cloudflare.com/_astro/1296x1296.BYEj6EvB.png) | ![fit=cover output](https://developers.cloudflare.com/_astro/pete-cover.ZWLczl5t.png) |
| --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| **Original**1080 x 720 (3:2)                                                            | **Requested**500 x 500 (1:1)                                                    | **Output**500 x 500 (1:1)                                                             |

When the original image is smaller than the target area, it upscales instead. To avoid upscaling, use `crop`.

#### `crop`

Resizes the image to fill the target area without upscaling.

When the original image is smaller than the target area, it keeps its original size and aspect ratio (like `scale-down`).

In the example below, the original image (1080x720) is smaller than the target area (1296x1296), so it preserves its original size and aspect ratio.

| ![original image](https://developers.cloudflare.com/_astro/pete-landscape.C1O-FBM1.jpg) | ![target area](https://developers.cloudflare.com/_astro/1296x1296.BYEj6EvB.png) | ![fit=crop output](https://developers.cloudflare.com/_astro/pete-landscape.C1O-FBM1.jpg) |
| --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| **Original**1080 x 720 (3:2)                                                            | **Requested**1296 x 1296 (1:1)                                                  | **Output**1080 x 720 (3:2)                                                               |

When the original image is larger than the target area, it behaves like `cover` (fills the target area and crops the rest) instead.

#### `aspect-crop`

Crops the image to match the target aspect ratio, scaling down if needed but never upscaling.

When the original image is larger than the target area, it downscales to the smallest size that still fills the target dimensions, then is cropped to match the target aspect ratio (like `cover`).

When the original image is smaller than the target area, it keeps its original size but is cropped to match the target aspect ratio. Unlike `crop`, which preserves the original size and dimensions of smaller images, `aspect-crop` always enforces the target aspect ratio.

For example, a 612x613 image requested at 1920x1120 will not be upscaled. Instead, it stays at its original size and is cropped to 612x357, matching the 1920:1120 aspect ratio. Use the [gravity](#gravity) parameter to control which part of the image is preserved during cropping.

#### `pad`

Resizes the image to be as large as possible within the dimensions. If applicable, the output area will be expanded to match the `width` and `height` dimensions exactly.

Works with the `background` parameter to fill any blank or transparent pixels. However, for web apps, you can often achieve the same visual result using the `contain` option with the CSS `object-fit: contain` property, which avoids encoding padding pixels into the image itself.

In the example below, the original image (1080x720) is smaller than the target area (1080x1080), so it creates space for the remaining pixels.

| ![original image](https://developers.cloudflare.com/_astro/pete-landscape.C1O-FBM1.jpg) | ![target area](https://developers.cloudflare.com/_astro/1296x1296.BYEj6EvB.png) | ![fit=pad output](https://developers.cloudflare.com/_astro/pete-pad.Ci9pnK3M.png) |
| --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| **Original**1080 x 720 (3:2)                                                            | **Requested**1080 x 1080 (1:1)                                                  | **Output**1080 x 1080 (1:1)                                                       |

#### `squeeze`

Resizes the image to exactly match the requested width and height, without cropping the edges or constraining the portions.

When the original and target aspect ratios differ, the image will be distorted to fit the target area.

| ![original image](https://developers.cloudflare.com/_astro/pete-landscape.C1O-FBM1.jpg) | ![fit=squeeze output](https://developers.cloudflare.com/_astro/pete-squeeze.BalgFHoW.jpg) |
| --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| **Original**1080 x 720                                                                  | **Output**1080 x 540                                                                      |

| ![original image](https://developers.cloudflare.com/_astro/abstract.D7Gt8U3T.jpg) | ![fit=squeeze output](https://developers.cloudflare.com/_astro/abstract-squeeze.COdyKF4Z.jpg) |
| --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| **Original**1080 x 1080                                                           | **Output**1080 x 540                                                                          |

#### `scale-up`

Resizes the image to fit within the specified dimensions while preserving its original aspect ratio, but never downscales the image. This is the inverse of `scale-down`.

When the original image is larger than the target area, it is returned at its original dimensions.

When the original image is smaller than the target area, it is enlarged to fit within the target dimensions. Use the [upscale](#upscale) parameter to control the algorithm used for upscaling images — set `upscale=generate` for AI-powered upscaling or `upscale=interpolate` (default) for bicubic interpolation.

### `flip`

Flips the image horizontally, vertically, or both.

Accepts the following values:

* `h` — Flips the image horizontally.
* `v` — Flips the image vertically.
* `hv` — Flips the image both horizontally and vertically.

Flip can be used with the `rotate` parameter to set the orientation of the image. Flip is performed before rotation. For example, if you apply `flip=h,rotate=90`, then the image will be flipped horizontally, then rotated by 90 degrees.

| ![Original image](https://developers.cloudflare.com/_astro/original.DuemPfHh.jpg) | ![flip=h output](https://developers.cloudflare.com/_astro/flip-h.Qc63pcHu.jpg) | ![flip=v output](https://developers.cloudflare.com/_astro/flip-v.DuCcWsSK.jpg) |
| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ |
| **Original**                                                                      | flip=h                                                                         | flip=v                                                                         |

* [ URL format ](#tab-panel-9216)
* [ Workers ](#tab-panel-9217)

```
flip=h
```

JavaScript

```
cf: {image: {flip: "h"}}
```

### `format` | `f`

Specifies the output format for the image.

Accepts the following values:

* `auto` — Automatically serves the most efficient format that the requesting browser supports. When you serve a [hosted image](https://developers.cloudflare.com/images/optimization/hosted-images/create-variants/), this is the default `format` option.
* `avif` — Transcodes the image to AVIF, if possible. AVIF encoding can be an order of magnitude slower than encoding to other formats. If the image is too large to be quickly encoded to AVIF, then Cloudflare will fall back to WebP or JPEG.
* `webp` — Transcodes the image to Google WebP format. Use `quality=100` to return the WebP lossless format.
* `jpeg` — Transcodes the image in interlaced progressive JPEG format, in which data is compressed in multiple passes of progressively higher detail.
* `baseline-jpeg` — Transcode the image in baseline sequential JPEG format. It should be used in cases when target devices do not support progressive JPEG or other modern file formats.
* `json` — Outputs information about the image as a JSON object. This contains data such as image size (before and after resizing), the source image's MIME type, and file size.

* [ URL format ](#tab-panel-9218)
* [ Workers ](#tab-panel-9219)

```
format=autof=auto
```

JavaScript

```
cf: {image: {format: "avif"}}
```

To use `format=auto` with a custom Worker, you need to parse the `Accept` header. Refer to [this example Worker](https://developers.cloudflare.com/images/optimization/transformations/transform-via-workers/#an-example-worker) for a complete overview of how to set up an image transformation Worker.

Custom Worker for Image Resizing with format:auto

```
const accept = request.headers.get("accept");let image = {};
if (/image\/avif/.test(accept)) {  image.format = "avif";} else if (/image\/webp/.test(accept)) {  image.format = "webp";}
return fetch(url, { cf: { image } });
```

### `gamma`

Adjusts the exposure of an image using a multiplier. Gamma controls the midtone brightness without affecting the darkest or lightest parts of the image.

* `0` and `1` (default) — No change to the original gamma.
* `< 1.0` — Increases midtone brightness, making the image appear lighter overall.
* `> 1.0` — Decreases midtone brightness, making the image appear darker overall.

| ![Original image](https://developers.cloudflare.com/_astro/original.DuemPfHh.jpg) | ![gamma=0.5 output](https://developers.cloudflare.com/_astro/gamma-0.5.Bcga364K.jpg) | ![gamma=2 output](https://developers.cloudflare.com/_astro/gamma-2.BrZb1hEC.jpg) |
| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------- |
| **Original**                                                                      | gamma=0.5                                                                            | gamma=2                                                                          |

* [ URL format ](#tab-panel-9220)
* [ Workers ](#tab-panel-9221)

```
gamma=0.5
```

JavaScript

```
cf: {image: {gamma: 0.5}}
```

### `gravity` | `g`

Specifies how the image should be cropped when used with `fit=cover` and `fit=crop`. By default, Cloudflare will crop toward the center point of the original image.

Accepts `auto`, `face`, a side (`left`, `right`, `top`, `bottom`), and relative coordinates (`XxY`).

* [ URL format ](#tab-panel-9222)
* [ Workers ](#tab-panel-9223)

```
gravity=autog=autogravity=facegravity=leftgravity=0.5x1
```

JavaScript

```
cf: {image: {gravity: "auto"}}cf: {image: {gravity: "face"}}cf: {image: {gravity: "left"}}cf: {image: {gravity: {x:0.5, y:0.2}}}
```

#### `auto`

Automatically sets the focal point by using a saliency algorithm to detect the most visually interesting pixels.

This is useful when you don't know the contents of the image ahead of time, such as with user-generated content. For large image libraries such as e-commerce product galleries, this feature eliminates the need to manually set a focal point for each image.

| ![original image](https://developers.cloudflare.com/_astro/coffee-base.B2l8XteX.jpg) | ![output without gravity=auto](https://developers.cloudflare.com/_astro/coffee-crop.BrOCyoCE.jpg) | ![output with gravity=auto](https://developers.cloudflare.com/_astro/coffee-auto.DAQPvZHc.jpg) |
| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| **Original**                                                                         | **Default crop**                                                                                  | gravity=auto                                                                                   |

#### `face`

Automatically sets the focal point based on faces in the image.

This can be combined with the [zoom](https://developers.cloudflare.com/images/optimization/features#zoom) parameter to specify how closely the image should be cropped toward the face.

| ![original image](https://developers.cloudflare.com/_astro/suad-kamardeen.O8gS5wPQ.jpeg) | ![output without gravity=face](https://developers.cloudflare.com/_astro/suad-kamardeen-crop.Btvf-dvP.jpeg) | ![output with gravity=face](https://developers.cloudflare.com/_astro/suad-kamardeen-face.3ZHIZdIY.jpeg) |
| ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| **Original**                                                                             | **Default crop**                                                                                           | gravity=face                                                                                            |

_Photograph by [Suad Kamardeen (@suadkamardeen) on Unsplash ↗](https://unsplash.com/photos/woman-in-black-cardigan-standing-beside-pink-flowers-UO-82DJ3rcc)_

#### `left`, `right`, `top`, `bottom`

Sets the side of the image that should not be cropped.

In the example below, the 1080x720 image is cropped to a 1080x400 area, starting from its bottom edge:

| ![original image](https://developers.cloudflare.com/_astro/pete-landscape.C1O-FBM1.jpg) | ![output without gravity=auto](https://developers.cloudflare.com/_astro/pete-bottom.BD_YS29E.jpg) |
| --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| **Original**                                                                            | gravity=bottom                                                                                    |

#### `XxY`

Sets the focal point (X,Y) so that the relative coordinates of the output image are positioned at the relative coordinates of the original image. Accepts a coordinate pair formatted as `XxY`, where X and Y are decimal values between `0.0` and `1.0`.

![Change the focal point using the relative coordinates](https://developers.cloudflare.com/_astro/xxy.pMA7L7Ny_2dl5dr.webp) 
* **Horizontal value (X)** — `0.0` is the left edge and `1.0` is the right edge of the image.
* **Vertical value (Y)** — `0.0` is the top edge and `1.0` is the bottom edge of the image.

The example below crops a 900x900 image to 300x900 using a 0.33x0.5 gravity point:

* Both the original image and target area will have gravity points set at 1/3 of the width from the left edge and 1/2 of the height from the top edge.
* The relative coordinates of the output gravity point are positioned at the relative coordinates of the original image. That is, the target area is positioned so that its gravity point sits at the same relative position in the original image (0.33, 0.5).
* The darkened parts of the image show the area outside of the requested output, which will be cropped.
* The final cropped result captures the 300x900 content that is around the gravity point (0.33, 0.5).

| ![original image](https://developers.cloudflare.com/_astro/base.DwQU1Wiz.png) | ![align gravity points on original and target area](https://developers.cloudflare.com/_astro/rel-points.tebmJ081.png) | ![crop using new gravity point](https://developers.cloudflare.com/_astro/rel-alignment.u2L4c77x.png) | ![final output](https://developers.cloudflare.com/_astro/rel-output.1DSTik2r.png) |
| ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| **Original**                                                                  | **Align**                                                                                                             | **Crop**                                                                                             | **Output**                                                                        |

When optimizing through Workers, use an object `{x, y}` to specify coordinates. For example, `{fit: "cover", gravity: {x:0.5, y:0.2}}` will crop each side to preserve as much as possible around a point at 20% of the height of the original image.

### `height` | `h`

Sets the height of the output image in pixels using a positive integer value. By default, Cloudflare uses the original height of the input image.

When `height` is set, the exact behavior depends on the `fit` parameter.

* [ URL format ](#tab-panel-9224)
* [ Workers ](#tab-panel-9225)

```
height=250h=250
```

JavaScript

```
cf: {image: {height: 250}}
```

### `metadata`

Controls the amount of invisible metadata (EXIF) that should be preserved for a JPEG image. For all other output formats (e.g. WebP or PNG), all metadata will always be discarded.

Color profiles and EXIF rotation are applied to the image even if the metadata is discarded.

Note

If [Polish](https://developers.cloudflare.com/images/polish/) is enabled, then all metadata may already be removed and this option will have no effect.

Accepts the following values:

* `copyright` (default) — Discards all metadata except EXIF copyright tag.
* `keep` — Preserves most of EXIF metadata, including GPS location, if present.
* `none` — Discards all invisible EXIF metadata.

* [ URL format ](#tab-panel-9226)
* [ Workers ](#tab-panel-9227)

```
metadata=none
```

JavaScript

```
cf: {image: {metadata: "none"}}
```

### `onerror`

Redirects the end-user to the URL of the original source image when a fatal error prevents the image from being transformed. Accepts `redirect`. The default is none.

Note

This feature is available only when optimizing remote images through the URL interface. This is not supported for hosted images.

This option works only if the image is in the same zone (subdomains are accepted). If the original image is from a different zone, then the option does not have any effect.

This may be useful in cases where an image requires user authentication and the image cannot be fetched anonymously via Workers. However, this option is not recommended if the source image is very large.

* [ URL format ](#tab-panel-9198)

```
onerror=redirect
```

### `quality` | `q`

Specifies the output quality of an image for JPEG, WebP, and AVIF formats, expressed as a fixed value or perceptual quality level. The default is `85`.

* **Fixed quality** — Accepts a positive integer from `1` (low quality, small file size) to `100` (high quality, large file size).
* **Perceptual quality** — Accepts `high`, `medium-high`, `medium-low`, and `low`.

When the output format is PNG, an explicit `quality` setting allows the use of PNG8 (palette) variant of the format.

* [ URL format ](#tab-panel-9228)
* [ Workers ](#tab-panel-9229)

```
quality=50quality=lowq=50
```

JavaScript

```
cf: {image: {quality: 50}}cf: {image: {quality: "high"}}
```

### `rotate`

Rotates an image by a number of degrees. Accepts `90`, `180`, or `270`. The default is `0` (no rotation).

Rotation is performed before resizing; `width` and `height` options will refer to the axes after the image is rotated.

| ![Original image](https://developers.cloudflare.com/_astro/original.DuemPfHh.jpg) | ![rotate=180 output](https://developers.cloudflare.com/_astro/rotate-180.Dx3iw1mv.jpg) |
| --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| **Original**                                                                      | rotate=180                                                                             |

* [ URL format ](#tab-panel-9230)
* [ Workers ](#tab-panel-9231)

```
rotate=90
```

JavaScript

```
cf: {image: {rotate: 90}}
```

### `saturation`

Adjusts the color saturation of an image using a multiplier.

* `0` — Completely desaturates the image (grayscale).
* `< 1.0` — Reduces color intensity. For example, `0.5` is half as saturated.
* `1` (default) — No change to the original saturation.
* `> 1.0` — Increases color intensity. For example, `2` is twice as saturated.

| ![Original image](https://developers.cloudflare.com/_astro/original.DuemPfHh.jpg) | ![saturation=0 output](https://developers.cloudflare.com/_astro/saturation-0.Cn9J5pmH.jpg) | ![saturation=2 output](https://developers.cloudflare.com/_astro/saturation-2.B7Pl0-wE.jpg) |
| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
| **Original**                                                                      | saturation=0                                                                               | saturation=2                                                                               |

* [ URL format ](#tab-panel-9232)
* [ Workers ](#tab-panel-9233)

```
saturation=0.5
```

JavaScript

```
cf: {image: {saturation: 0.5}}
```

### `segment`

Automatically isolates the subject of an image by replacing the background with transparent pixels. Accepts `foreground`. The default is none.

This feature uses an open-source model called BiRefNet through [Workers AI](https://developers.cloudflare.com/workers-ai/). Read more about Cloudflare's [approach to responsible AI ↗](https://www.cloudflare.com/trust-hub/responsible-ai/).

| ![Original image](https://developers.cloudflare.com/_astro/original.DuemPfHh.jpg) | ![segment=foreground output](https://developers.cloudflare.com/_astro/segment-foreground.B6UYNLDs.png) |
| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| **Original**                                                                      | segment=foreground                                                                                     |

* [ URL format ](#tab-panel-9234)
* [ Workers ](#tab-panel-9235)

```
segment=foreground
```

JavaScript

```
cf: {image: {segment: "foreground"}}
```

### `sharpen`

Applies a sharpening filter to enhance edge definition in an image. Accepts a decimal value from `0` (no sharpening) to `10` (maximum sharpening). The default is `0`. The recommended value for downscaled images is `1`.

| ![Original image](https://developers.cloudflare.com/_astro/original.DuemPfHh.jpg) | ![sharpen=5 output](https://developers.cloudflare.com/_astro/sharpen-5.CyEreNja.jpg) |
| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| **Original**                                                                      | sharpen=5                                                                            |

* [ URL format ](#tab-panel-9236)
* [ Workers ](#tab-panel-9237)

```
sharpen=2
```

JavaScript

```
cf: {image: {sharpen: 2}}
```

### `slow-connection-quality` | `scq`

Overrides the `quality` value whenever a slow connection is detected. Accepts the same fixed or perceptual settings as [quality](#quality). The default is none.

Note

This feature is available only when optimizing through the URL interface on Chromium-based browsers such as Chrome, Edge, and Opera.

To detect slow connections, enable any of the following client hints via HTTP in a header:

```
accept-ch: rtt, save-data, ect, downlink
```

`slow-connection-quality` applies when the client hint is present and any of the following conditions are met:

* [rtt ↗](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/RTT): Greater than 150ms.
* [save-data ↗](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Save-Data): Value is "on".
* [ect ↗](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ECT): Value is one of `slow-2g|2g|3g`.
* [downlink ↗](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Downlink): Less than 5Mbps.

* [ URL format ](#tab-panel-9199)

```
slow-connection-quality=50scq=50
```

### `trim`

Removes pixels around the sides of an image.

This feature can be used to trim an image by its border colors or by a specified number of pixels from its side(s).

Trim takes into account the [dpr](#dpr) parameter and is performed before resizing and rotation.

#### `border`

Automatically trims the sides of the image based on its border color.

The `trim=border` option can be further adjusted using the following parameters:

* `trim.border.color` — Selects the border color to trim. Accepts any CSS color using CSS4 modern syntax. If omitted, the color is detected automatically.
* `trim.border.tolerance` — Sets how closely the detected pixels must match in color. Accepts an integer between `0` (doesn't need to match) and 255 (must match exactly).
* `trim.border.keep` — Specifies the number of pixels of the original border to leave untrimmed.

#### `top;right;bottom;left`

Specifies the number of pixels to remove from the sides of an image. Accepts four values, separated by a semicolon, to set the trim on all four sides of an image at once.

All trim values accept either an integer (pixel count) or a decimal between `0` and `1` representing a fraction of the image dimension. For example, `0.25` trims 25% from that side.

Trim can also be applied to a specific side using the following parameters:

* `trim.top` — Removes pixels from the top of the image.
* `trim.left` — Removes pixels from the left of the image.
* `trim.height` — Sets the height of the image from the top edge, then trims everything below.
* `trim.width` — Sets the width of the image from the left edge, then trims everything to the right.

* [ URL format ](#tab-panel-9238)
* [ Workers ](#tab-panel-9239)

```
trim=bordertrim.height=800// This sets the height of the image to 800 pixels from the top of the image, then trims everything below that point
trim.left=800// This removes 800 pixels from the left of the image
trim=0.1;0.2;0.1;0.2// This trims 10% from the top and bottom, and 20% from the left and right
trim.top=0.25// This trims 25% of the image height from the top
```

JavaScript

```
cf: {image: {trim: {top: 12, right: 78, bottom: 34, left: 56, width: 678, height: 678}}}// Using decimals to trim 10% from each side:cf: {image: {trim: {top: 0.1, right: 0.1, bottom: 0.1, left: 0.1}}}
```

### `upscale`

Controls the algorithm used when an image needs to be enlarged. This parameter works with any [fit](#fit) mode that upscales, such as [contain](#contain), [cover](#cover), and [scale-up](#scale-up). It has no effect when `fit=scale-down` or when the target dimensions are smaller than the source.

Accepts the following values:

* `interpolate` (default) — Uses bicubic interpolation, which may reduce image quality. This is the default behavior when `upscale` is not specified.
* `generate` — Uses AI upscaling ([ESRGAN ↗](https://github.com/xinntao/ESRGAN)) to produce sharper, more detailed results when enlarging images.

When `upscale=generate` is specified, the AI model runs a single pass at the nearest supported scale (2x or 4x), then adjusts to the exact target dimensions. Scale factors beyond 4x are handled with AI upscaling to 4x, then bicubic interpolation for the remainder.

Note

`upscale=generate` has higher latency than `upscale=interpolate` due to GPU inference. Results are cached following the same [caching rules](https://developers.cloudflare.com/images/optimization/features/#caching) as other optimizations.

* [ URL format ](#tab-panel-9240)
* [ Workers ](#tab-panel-9241)

```
upscale=generate
```

JavaScript

```
cf: {image: {upscale: "generate"}}
```

### `width` | `w`

Sets the width of the output image in pixels using a positive integer value. By default, Cloudflare uses the original width of the input image.

When `width` is set, the exact behavior depends on the `fit` parameter.

Accepts the following values:

* A number in pixels (for example, `250`).
* `auto` — Automatically serves the image in the most optimal width based on available information about the browser and device. Accepts `wbreakpoints` (client hints), `wmobile` (user-agent detection), and `wdesktop` (user-agent detection) as sub-parameters.

* [ URL format ](#tab-panel-9242)
* [ Workers ](#tab-panel-9243)

```
width=250w=250
```

JavaScript

```
cf: {image: {width: 250}}
```

#### `width=auto` sub-parameters

When `width=auto` is specified, Cloudflare resizes the image using information from client hints (sent by the browser) or by user-agent detection as a fallback.

You can customize the `width=auto` behavior with the following sub-parameters:

| Sub-parameter | Description                                                                   | Default          |
| ------------- | ----------------------------------------------------------------------------- | ---------------- |
| wbreakpoints  | Override default breakpoint widths, in pixels (client hints)                  | 320;768;960;1200 |
| wmobile       | Override default width, in pixels, for mobile devices (user-agent detection)  | 768              |
| wdesktop      | Override default width, in pixels, for desktop devices (user-agent detection) | 1200             |

When optimizing remote images with `width=auto`, each unique width counts as a separate [billable transformation](https://developers.cloudflare.com/images/pricing/#images-transformed).

To learn how `width=auto` works, refer to our guide on [serving responsive images](https://developers.cloudflare.com/images/optimization/make-responsive-images/).

* [ URL format ](#tab-panel-9244)
* [ Workers ](#tab-panel-9245)

```
wbreakpoints=320;768;960;1920 // Changes the largest breakpoint to 1920 pixelswbreakpoints=320;768;960;1200;1920 // Adds another breakpoint at 1920 pixels
```

JavaScript

```
cf: {image: {wbreakpoints: "320;768;960;1920"}}
```

### `zoom` | `face-zoom`

Specifies how closely the image is cropped toward detected faces when combined with the `gravity=face` option. Accepts a valid range between `0.0` (includes as much of the background as possible) and `1.0` (crops the image as closely to the face as possible). The default is `0`.

* [ URL format ](#tab-panel-9246)
* [ Workers ](#tab-panel-9247)

```
zoom=0.1
```

JavaScript

```
cf: {image: {zoom: 0.5}}
```

## Recommended image sizes

Ideally, image sizes should match the exact size that they are displayed on the page. If the page contains thumbnails with markup such as `<img width="200" …>`, then images should be resized to `width=200`.

To [serve responsive images](https://developers.cloudflare.com/images/optimization/make-responsive-images/), you can use the HTML `srcset` attribute to let the provider pick the most optimal size. If you can't use the `<img srcset>` markup and have to hardcode specific maximum sizes, Cloudflare recommends the following sizes:

* Maximum of 1920 pixels for desktop browsers.
* Maximum of 960 pixels for tablets.
* Maximum of 640 pixels for mobile phones.

For example, `fit=scale-down,width=1920` sets a maximum size of 1920px and ensures that the image will not be enlarged unnecessarily.

You can detect device type by enabling the `CF-Device-Type` header [via Cache Rule](https://developers.cloudflare.com/cache/how-to/cache-rules/examples/cache-device-type/).

## Caching

When you optimize with Images, the original image will be fetched from the origin server and cached — following the usual rules of HTTP caching, `Cache-Control` header, etc.. Requests for multiple different image sizes are likely to reuse the cached original image without causing extra transfers from the origin server.

If [Custom Cache Keys](https://developers.cloudflare.com/cache/how-to/cache-keys/) are used for the origin image, the origin image might not be cached and might result in more calls to the origin.

Optimized images follow the same caching rules as the original image they were resized from, except the minimum cache time is one hour. If you need images to be updated more frequently, add `must-revalidate` to the `Cache-Control` header. The Images service supports cache revalidation, so we recommend serving images with the `Etag` header. Refer to the [Cache docs for more information](https://developers.cloudflare.com/cache/concepts/cache-control/#revalidation).

Cloudflare does not support purging optimized images individually. URLs starting with `/cdn-cgi/` cannot be purged. However, purging of the original image's URL will also purge all of its optimized versions.

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/optimization/features/#page","headline":"Features · Cloudflare Images docs","description":"Available Cloudflare Images optimization parameters for resizing, cropping, format conversion, and visual effects.","url":"https://developers.cloudflare.com/images/optimization/features/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-06-16","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/optimization/","name":"Optimization"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/optimization/features/","name":"Features"}}]}
```

---

---
title: Apply blur
description: Add a blur effect to Cloudflare Images variants using the dashboard or flexible variants API.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Apply blur

You can apply blur to image variants by creating a specific variant for this effect first or by editing a previously created variant. Note that you cannot blur an SVG file.

Refer to [Resize images](https://developers.cloudflare.com/images/optimization/hosted-images/create-variants/) for help creating variants. You can also refer to the API to learn how to use blur using flexible variants.

To blur an image:

1. In the Cloudflare dashboard, go to the **Hosted Images** page.  
[ Go to **Hosted images** ](https://dash.cloudflare.com/?to=/:account/images/hosted)
2. Select the **Delivery** tab.
3. Find the variant you want to blur and select **Edit** \> **Customization Options**.
4. Use the slider to adjust the blurring effect. You can use the preview image to see how strong the blurring effect will be.
5. Select **Save**.

The image should now display the blurred effect.

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/optimization/hosted-images/blur-variants/#page","headline":"Apply blur · Cloudflare Images docs","description":"Add a blur effect to Cloudflare Images variants using the dashboard or flexible variants API.","url":"https://developers.cloudflare.com/images/optimization/hosted-images/blur-variants/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/optimization/","name":"Optimization"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/optimization/hosted-images/","name":"Hosted images"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/optimization/hosted-images/blur-variants/","name":"Apply blur"}}]}
```

---

---
title: Browser TTL
description: Configure cache-control settings for Cloudflare Images at the account or variant level.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Browser TTL

Browser TTL controls how long an image stays in a browser's cache and specifically configures the `cache-control` response header.

### Default TTL

By default, an image's TTL is set to two days to meet user needs, such as re-uploading an image under the same [Custom ID](https://developers.cloudflare.com/images/storage/upload-images/upload-custom-path/).

## Custom setting

You can use two custom settings to control the Browser TTL, an account or a named variant. To adjust how long a browser should keep an image in the cache, set the TTL in seconds, similar to how the `max-age` header is set. The value should be an interval between one hour to one year.

### Browser TTL for an account

Setting the Browser TTL per account overrides the default TTL.

Example

```
curl --request PATCH 'https://api.cloudflare.com/client/v4/accounts/{account_id}/images/v1/config' \--header "Authorization: Bearer <API_TOKEN>" \--header "Content-Type: application/json" \--data '{  "browser_ttl": 31536000}'
```

When the Browser TTL is set to one year for all images, the response for the `cache-control` header is essentially `public`, `max-age=31536000`, `stale-while-revalidate=7200`.

### Browser TTL for a named variant

Setting the Browser TTL for a named variant is a more granular option that overrides all of the above when creating or updating an image variant, specifically the `browser_ttl` option in seconds.

Example

```
curl 'https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_TAG>/images/v1/variants' \--header "Authorization: Bearer <API_TOKEN>" \--header "Content-Type: application/json" \--data '{  "id":"avatar",  "options": {    "width":100,    "browser_ttl": 86400  }}'
```

When the Browser TTL is set to one day for images requested with this variant, the response for the `cache-control` header is essentially `public`, `max-age=86400`, `stale-while-revalidate=7200`.

Note

[Private images](https://developers.cloudflare.com/images/optimization/hosted-images/serve-private-images/) do not respect default or custom TTL settings. The private images cache time is set according to the expiration time and can be as short as one hour.

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/optimization/hosted-images/browser-ttl/#page","headline":"Browser TTL · Cloudflare Images docs","description":"Configure cache-control settings for Cloudflare Images at the account or variant level.","url":"https://developers.cloudflare.com/images/optimization/hosted-images/browser-ttl/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/optimization/","name":"Optimization"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/optimization/hosted-images/","name":"Hosted images"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/optimization/hosted-images/browser-ttl/","name":"Browser TTL"}}]}
```

---

---
title: Create predefined variants
description: Define named variants in Cloudflare Images to control how hosted images are resized and served.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Create predefined variants

Variants let you specify how images should be resized for different use cases. By default, images are served with a `public` variant, but you can create up to 100 variants to fit your needs. Follow these steps to create a variant.

Note

Cloudflare Images can deliver SVG files but will not resize them because it is an inherently scalable format. Resize via the Cloudflare dashboard.

1. In the Cloudflare dashboard, go to the **Hosted Images** page.  
[ Go to **Hosted images** ](https://dash.cloudflare.com/?to=/:account/images/hosted)
2. Select the **Delivery** tab.
3. Select **Create variant**.
4. Name your variant and select **Create**.
5. Define variables for your new variant, such as resizing options, type of fit, and specific metadata options.

## Resize via the API

Make a `POST` request to [create a variant](https://developers.cloudflare.com/api/resources/images/subresources/v1/subresources/variants/methods/create/).

Terminal window

```
curl "https://api.cloudflare.com/client/v4/accounts/{account_id}/images/v1/variants" \--header "Authorization: Bearer <API_TOKEN>" \--header "Content-Type: application/json" \--data '{"id":"<NAME_OF_THE_VARIANT>","options":{"fit":"scale-down","metadata":"none","width":1366,"height":768},"neverRequireSignedURLs":true}
```

## Fit options

The `Fit` property describes how the width and height dimensions should be interpreted. The chart below describes each of the options.

| Fit Options | Behavior                                                                                                                                                                                                                                                                    |
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Scale down  | The image is shrunk in size to fully fit within the given width or height, but will not be enlarged.                                                                                                                                                                        |
| Contain     | The image is resized (shrunk or enlarged) to be as large as possible within the given width or height while preserving the aspect ratio.                                                                                                                                    |
| Cover       | The image is resized to exactly fill the entire area specified by width and height and will be cropped if necessary.                                                                                                                                                        |
| Crop        | The image is shrunk and cropped to fit within the area specified by the width and height. The image will not be enlarged. For images smaller than the given dimensions, it is the same as scale-down. For images larger than the given dimensions, it is the same as cover. |
| Pad         | The image is resized (shrunk or enlarged) to be as large as possible within the given width or height while preserving the aspect ratio. The extra area is filled with a background color (white by default).                                                               |

## Metadata options

Variants allow you to choose what to do with your image’s metadata information. From the **Metadata** dropdown, choose:

* Strip all metadata
* Strip all metadata except copyright
* Keep all metadata

## Public access

When the **Always allow public access** option is selected, particular variants will always be publicly accessible, even when images are made private through the use of [signed URLs](https://developers.cloudflare.com/images/optimization/hosted-images/serve-private-images/).

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/optimization/hosted-images/create-variants/#page","headline":"Create predefined variants · Cloudflare Images docs","description":"Define named variants in Cloudflare Images to control how hosted images are resized and served.","url":"https://developers.cloudflare.com/images/optimization/hosted-images/create-variants/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/optimization/","name":"Optimization"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/optimization/hosted-images/","name":"Hosted images"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/optimization/hosted-images/create-variants/","name":"Create predefined variants"}}]}
```

---

---
title: Delete variants
description: Remove image variants from Cloudflare Images using the dashboard or API.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Delete variants

You can delete variants via the Images dashboard or API. The only variant you cannot delete is public.

Warning

Deleting a variant is a global action that will affect other images that contain that variant.

## Delete variants via the Cloudflare dashboard

1. In the Cloudflare dashboard, go to the **Hosted Images** page.  
[ Go to **Hosted images** ](https://dash.cloudflare.com/?to=/:account/images/hosted)
2. Select the **Delivery** tab.
3. Find the variant you want to remove and select **Delete**.

## Delete variants via the API

Make a `DELETE` request to the delete variant endpoint.

Terminal window

```
curl --request DELETE https://api.cloudflare.com/client/v4/accounts/{account_id}/images/v1/variants/{variant_name} \--header "Authorization: Bearer <API_TOKEN>"
```

After the variant has been deleted, the response returns `"success": true.`

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/optimization/hosted-images/delete-variants/#page","headline":"Delete variants · Cloudflare Images docs","description":"Remove image variants from Cloudflare Images using the dashboard or API.","url":"https://developers.cloudflare.com/images/optimization/hosted-images/delete-variants/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/optimization/","name":"Optimization"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/optimization/hosted-images/","name":"Hosted images"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/optimization/hosted-images/delete-variants/","name":"Delete variants"}}]}
```

---

---
title: Enable flexible variants
description: Turn on flexible variants in Cloudflare Images to allow dynamic resizing beyond predefined variant options.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Enable flexible variants

Flexible variants allow you to create variants with dynamic resizing which can provide more options than regular variants allow. This option is not enabled by default.

## Enable flexible variants via the Cloudflare dashboard

1. In the Cloudflare dashboard, go to the **Hosted Images** page.  
[ Go to **Hosted images** ](https://dash.cloudflare.com/?to=/:account/images/hosted)
2. Select the **Delivery** tab.
3. Enable **Flexible variants**.

## Enable flexible variants via the API

Make a `PATCH` request to the [Update a variant endpoint](https://developers.cloudflare.com/api/resources/images/subresources/v1/subresources/variants/methods/edit/).

Terminal window

```
curl --request PATCH https://api.cloudflare.com/client/v4/accounts/{account_id}/images/v1/config \--header "Authorization: Bearer <API_TOKEN>" \--header "Content-Type: application/json" \--data '{"flexible_variants": true}'
```

After activation, you can use [optimization parameters](https://developers.cloudflare.com/images/optimization/features/#parameters) on any Cloudflare image. For example,

`https://imagedelivery.net/{account_hash}/{image_id}/w=400,sharpen=3`

Note

Flexible variants cannot be used for images that require a [signed delivery URL](https://developers.cloudflare.com/images/optimization/hosted-images/serve-private-images/).

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/optimization/hosted-images/enable-flexible-variants/#page","headline":"Enable flexible variants · Cloudflare Images docs","description":"Turn on flexible variants in Cloudflare Images to allow dynamic resizing beyond predefined variant options.","url":"https://developers.cloudflare.com/images/optimization/hosted-images/enable-flexible-variants/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/optimization/","name":"Optimization"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/optimization/hosted-images/","name":"Hosted images"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/optimization/hosted-images/enable-flexible-variants/","name":"Enable flexible variants"}}]}
```

---

---
title: Serve images from custom domains
description: Deliver Cloudflare Images through your own custom domain using the cdn-cgi image delivery path.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Serve images from custom domains

Image delivery is supported from all customer domains under the same Cloudflare account. To serve images through custom domains, an image URL should be adjusted to the following format:

```
https://example.com/cdn-cgi/imagedelivery/<ACCOUNT_HASH>/<IMAGE_ID>/<VARIANT_NAME>
```

Example with a custom domain:

```
https://example.com/cdn-cgi/imagedelivery/ZWd9g1K7eljCn_KDTu_MWA/083eb7b2-5392-4565-b69e-aff66acddd00/public
```

In this example, `<ACCOUNT_HASH>`, `<IMAGE_ID>` and `<VARIANT_NAME>` are the same, but the hostname and prefix path is different:

* `example.com`: Cloudflare proxied domain under the same account as the Cloudflare Images.
* `/cdn-cgi/imagedelivery`: Path to trigger `cdn-cgi` image proxy.
* `ZWd9g1K7eljCn_KDTu_MWA`: The Images account hash. This can be found in the Cloudflare Images Dashboard.
* `083eb7b2-5392-4565-b69e-aff66acddd00`: The image ID.
* `public`: The variant name.

## Custom paths

By default, Images are served from the `/cdn-cgi/imagedelivery/` path. You can use [Transform Rules](https://developers.cloudflare.com/rules/transform/) to rewrite URLs and serve images from custom paths.

### Basic version

Free and Pro plans support string matching rules (including wildcard operations) that do not require regular expressions.

This example lets you rewrite a request from `example.com/images` to `example.com/cdn-cgi/imagedelivery/<ACCOUNT_HASH>`.

To create a rule:

1. In the Cloudflare dashboard, go to the **Rules Overview** page.  
[ Go to **Overview** ](https://dash.cloudflare.com/?to=/:account/:zone/rules/overview)
2. Next to **URL Rewrite Rules**, select **Create rule**.
3. Under **If incoming requests match**, select **Wildcard pattern** and enter the following **Request URL** (update with your own domain):  
```  
https://example.com/images/*  
```
4. Under **Then rewrite the path and/or query** \> **Path**, enter the following values (using your account hash):

  * **Target path**: \[`/`\] `images/*`
  * **Rewrite to**: \[`/`\] `cdn-cgi/imagedelivery/<ACCOUNT_HASH>/${1}`
5. Select **Deploy** when you are done.

### Advanced version

Note

This feature requires a Business or Enterprise plan to enable regular expressions in Transform Rules. Refer to Cloudflare [Transform Rules Availability](https://developers.cloudflare.com/rules/transform/#availability) for more information.

This example lets you rewrite a request from `example.com/images/some-image-id/w100,h300` to `example.com/cdn-cgi/imagedelivery/<ACCOUNT_HASH>/some-image-id/width=100,height=300` and assumes Flexible variants feature is turned on.

To create a rule:

1. In the Cloudflare dashboard, go to the **Rules Overview** page.  
[ Go to **Overview** ](https://dash.cloudflare.com/?to=/:account/:zone/rules/overview)
2. Next to **URL Rewrite Rules**, select **Create rule**.
3. Under **If incoming requests match**, select **Custom filter expression** and then select **Edit expression**.
4. In the text field, enter `(http.request.uri.path matches "^/images/.*$")`.
5. Under **Path**, select **Rewrite to**.
6. Select _Dynamic_ and enter the following in the text field.

```
regex_replace(  http.request.uri.path,  "^/images/(.*)\\?w([0-9]+)&h([0-9]+)$",  "/cdn-cgi/imagedelivery/<ACCOUNT_HASH>/${1}/width=${2},height=${3}")
```

## Limitations

When using a custom domain, it is not possible to directly set up WAF rules that act on requests hitting the `/cdn-cgi/imagedelivery/` path. If you need to set up WAF rules, you can use a Cloudflare Worker to access your images and a Route using your domain to execute the worker. For an example worker, refer to [Serve private images using signed URL tokens](https://developers.cloudflare.com/images/optimization/hosted-images/serve-private-images/).

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/optimization/hosted-images/serve-from-custom-domains/#page","headline":"Serve images from custom domains · Cloudflare Images docs","description":"Deliver Cloudflare Images through your own custom domain using the cdn-cgi image delivery path.","url":"https://developers.cloudflare.com/images/optimization/hosted-images/serve-from-custom-domains/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/optimization/","name":"Optimization"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/optimization/hosted-images/","name":"Hosted images"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/optimization/hosted-images/serve-from-custom-domains/","name":"Serve images from custom domains"}}]}
```

---

---
title: Serve private images
description: Restrict access to Cloudflare Images by generating signed URL tokens with expiration times.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Serve private images

You can serve private images by using signed URL tokens. When an image requires a signed URL, the image cannot be accessed without a token unless it is being requested for a variant set to always allow public access.

1. In the Cloudflare dashboard, go to the **Hosted Images** page.  
[ Go to **Hosted images** ](https://dash.cloudflare.com/?to=/:account/images/hosted)
2. Select **Keys**.
3. Copy your key and use it to generate an expiring tokenized URL.

Note

Private images do not currently support custom paths.

## Generate signed URLs from your backend

Signed URLs are generated server-side to protect your signing key. The example below uses a Cloudflare Worker, but the same signing logic can be implemented in any backend environment (Node.js, Python, PHP, Go, etc.).

The Worker accepts a regular Images URL and returns a signed URL that expires after one day. Adjust the `EXPIRATION` value to set a different expiry period.

Note

Never hardcode your signing key in source code. Store it as a secret using [npx wrangler secret put](https://developers.cloudflare.com/workers/wrangler/commands/general/#secret) and access it via the `env` parameter. For more information, refer to [Secrets](https://developers.cloudflare.com/workers/configuration/secrets/).

* [  JavaScript ](#tab-panel-9250)
* [  TypeScript ](#tab-panel-9251)

JavaScript

```
const EXPIRATION = 60 * 60 * 24; // 1 day
const bufferToHex = (buffer) =>  [...new Uint8Array(buffer)]    .map((x) => x.toString(16).padStart(2, "0"))    .join("");
async function generateSignedUrl(url, signingKey) {  // `url` is a full imagedelivery.net URL  // e.g. https://imagedelivery.net/cheeW4oKsx5ljh8e8BoL2A/bc27a117-9509-446b-8c69-c81bfeac0a01/mobile
  const encoder = new TextEncoder();  const secretKeyData = encoder.encode(signingKey);  const key = await crypto.subtle.importKey(    "raw",    secretKeyData,    { name: "HMAC", hash: "SHA-256" },    false,    ["sign"],  );
  // Attach the expiration value to the URL  const expiry = Math.floor(Date.now() / 1000) + EXPIRATION;  url.searchParams.set("exp", expiry);  // `url` now looks like  // https://imagedelivery.net/cheeW4oKsx5ljh8e8BoL2A/bc27a117-9509-446b-8c69-c81bfeac0a01/mobile?exp=1631289275
  const stringToSign = url.pathname + "?" + url.searchParams.toString();  // e.g. /cheeW4oKsx5ljh8e8BoL2A/bc27a117-9509-446b-8c69-c81bfeac0a01/mobile?exp=1631289275
  // Generate the HMAC signature  const mac = await crypto.subtle.sign(    "HMAC",    key,    encoder.encode(stringToSign),  );  const sig = bufferToHex(new Uint8Array(mac).buffer);
  // Attach the signature to the URL  url.searchParams.set("sig", sig);
  return new Response(url);}
export default {  async fetch(request, env, ctx) {    const url = new URL(request.url);    const imageDeliveryURL = new URL(      url.pathname        .slice(1)        .replace("https:/imagedelivery.net", "https://imagedelivery.net"),    );    // IMAGES_SIGNING_KEY is set via `npx wrangler secret put IMAGES_SIGNING_KEY`    return generateSignedUrl(imageDeliveryURL, env.IMAGES_SIGNING_KEY);  },};
```

TypeScript

```
const EXPIRATION = 60 * 60 * 24; // 1 day
const bufferToHex = (buffer: ArrayBuffer) =>  [...new Uint8Array(buffer)]    .map((x) => x.toString(16).padStart(2, "0"))    .join("");
async function generateSignedUrl(  url: URL,  signingKey: string,): Promise<Response> {  // `url` is a full imagedelivery.net URL  // e.g. https://imagedelivery.net/cheeW4oKsx5ljh8e8BoL2A/bc27a117-9509-446b-8c69-c81bfeac0a01/mobile
  const encoder = new TextEncoder();  const secretKeyData = encoder.encode(signingKey);  const key = await crypto.subtle.importKey(    "raw",    secretKeyData,    { name: "HMAC", hash: "SHA-256" },    false,    ["sign"],  );
  // Attach the expiration value to the URL  const expiry = Math.floor(Date.now() / 1000) + EXPIRATION;  url.searchParams.set("exp", expiry);  // `url` now looks like  // https://imagedelivery.net/cheeW4oKsx5ljh8e8BoL2A/bc27a117-9509-446b-8c69-c81bfeac0a01/mobile?exp=1631289275
  const stringToSign = url.pathname + "?" + url.searchParams.toString();  // e.g. /cheeW4oKsx5ljh8e8BoL2A/bc27a117-9509-446b-8c69-c81bfeac0a01/mobile?exp=1631289275
  // Generate the HMAC signature  const mac = await crypto.subtle.sign(    "HMAC",    key,    encoder.encode(stringToSign),  );  const sig = bufferToHex(new Uint8Array(mac).buffer);
  // Attach the signature to the URL  url.searchParams.set("sig", sig);
  return new Response(url);}
export default {  async fetch(request, env, ctx): Promise<Response> {    const url = new URL(request.url);    const imageDeliveryURL = new URL(      url.pathname        .slice(1)        .replace("https:/imagedelivery.net", "https://imagedelivery.net"),    );    // IMAGES_SIGNING_KEY is set via `npx wrangler secret put IMAGES_SIGNING_KEY`    return generateSignedUrl(imageDeliveryURL, env.IMAGES_SIGNING_KEY);  },} satisfies ExportedHandler<Env>;
```

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/optimization/hosted-images/serve-private-images/#page","headline":"Serve private images · Cloudflare Images docs","description":"Restrict access to Cloudflare Images by generating signed URL tokens with expiration times.","url":"https://developers.cloudflare.com/images/optimization/hosted-images/serve-private-images/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/optimization/","name":"Optimization"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/optimization/hosted-images/","name":"Hosted images"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/optimization/hosted-images/serve-private-images/","name":"Serve private images"}}]}
```

---

---
title: Serve uploaded images
description: Construct delivery URLs to serve images uploaded to Cloudflare Images using your account hash, image ID, and variant name.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Serve uploaded images

To serve images uploaded to Cloudflare Images, you must have:

* Your Images account hash
* Image ID
* Variant or flexible variant name

Assuming you have at least one image uploaded to Images, you will find the basic URL format from the Images dashboard under Developer Resources.

![Developer Resources section within the Images product form the Cloudflare Dashboard.](https://developers.cloudflare.com/_astro/image-delivery-url.D7G6zX-5_o6j6Y.webp) 

A typical image delivery URL looks similar to the example below.

`https://imagedelivery.net/<ACCOUNT_HASH>/<IMAGE_ID>/<VARIANT_NAME>`

In the example, you need to replace `<ACCOUNT_HASH>` with your Images account hash, along with the `<IMAGE_ID>` and `<VARIANT_NAME>`, to begin serving images.

You can select **Preview** next to the image you want to serve to preview the image with an Image URL you can copy. The link will have a fully formed **Images URL** and will look similar to the example below.

In this example:

* `ZWd9g1K7eljCn_KDTu_MWA` is the Images account hash.
* `083eb7b2-5392-4565-b69e-aff66acddd00` is the image ID. You can also use Custom IDs instead of the generated ID.
* `public` is the variant name.

When a user requests an image, Cloudflare Images chooses the optimal format, which is determined by client headers and the image type.

## Optimize format

Cloudflare Images automatically transcodes uploaded PNG, JPEG and GIF files to the more efficient AVIF and WebP formats. This happens whenever the customer browser supports them. If the browser does not support AVIF, Cloudflare Images will fall back to WebP. If there is no support for WebP, then Cloudflare Images will serve compressed files in the original format.

Uploaded SVG files are served as [sanitized SVGs](https://developers.cloudflare.com/images/get-started/limits/#svg).

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/optimization/hosted-images/serve-uploaded-images/#page","headline":"Serve uploaded images · Cloudflare Images docs","description":"Construct delivery URLs to serve images uploaded to Cloudflare Images using your account hash, image ID, and variant name.","url":"https://developers.cloudflare.com/images/optimization/hosted-images/serve-uploaded-images/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/optimization/","name":"Optimization"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/optimization/hosted-images/","name":"Hosted images"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/optimization/hosted-images/serve-uploaded-images/","name":"Serve uploaded images"}}]}
```

---

---
title: Make responsive images
description: Automatically resize images for optimal display on every device.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Make responsive images

Responsive design scales media elements to fit the screen they are displayed on.

Without it, images can overflow their container and break the layout on small screens, look blurry on high-density displays, and waste bandwidth by forcing every device to download the same oversized file.

You can use Images to automatically resize images for optimal display on every device. Cloudflare supports two ways to serve responsive images on request:

| Approach                                   | How it works                                                                                                      | Best for                                                                   |
| ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| [HTML srcset](#using-the-srcset-attribute) | List multiple sizes in markup and let the browser pick the best match based on viewport size and display density. | Full control over which sizes are available. Works in all browsers.        |
| [width=auto](#using-widthauto)             | Cloudflare automatically selects the best width from a single URL — no markup changes required.                   | Simplest implementation, especially when you don't have control over HTML. |

## Optimize for high-DPI displays

A screen displays images using physical pixels (the individual dots that you see), while the browser uses CSS pixels (an abstract unit used for layout).

On a standard display, these map 1:1\. On high-density displays (for example, Retina, 4K), each CSS pixel is rendered using multiple physical pixels — for example, 4 physical pixels on a 2x display, 9 on a 3x display.

This ratio — the device pixel ratio (DPR) — determines how sharp an image will appear. When you serve a 960px image on a 2x display, the browser stretches it across 1920 physical pixels, making it appear blurry.

To keep images sharp, you can provide a separate, higher-resolution version for high-DPI screens using the [dpr](https://developers.cloudflare.com/images/optimization/features/#dpr) parameter:

| ![dpr=1 output](https://developers.cloudflare.com/_astro/dpr-1.kw44tjdd.jpg) | ![dpr=2 output](https://developers.cloudflare.com/_astro/dpr-2.frEHI63e.jpg) |
| ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| width=300,height=200,dpr=1                                                   | width=300,height=200,dpr=2                                                   |

## Use the `srcset` attribute

When you embed an image using an `<img>` element, you can use its [srcset ↗](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/img#srcset) attribute to give the browser a list of the same image at different sizes.

The browser evaluates screen size, pixel density, and network conditions, then selects the single best match.

The snippet below shows how `srcset` can be used within an `<img>` tag to serve one of two possible sizes, depending on the user's device pixel ratio:

```
<img  src="portrait-800w.jpg"  srcset="    portrait-1600.jpg 2x,  "/>
```

Instead of pre-generating each size, use Images to point every `srcset` entry at the same source image with a different `width` parameter and pixel density descriptor (for example, `2x`). Once the browser selects the right width for the user's device pixel ratio, Cloudflare dynamically generates the resized version on request:

```
<img  src="/cdn-cgi/image/fit=contain,width=960/assets/product.jpg"  srcset="/cdn-cgi/image/fit=contain,width=1920/assets/product.jpg 2x"/>
```

In the example above, the `src` attribute contains the image for 1x displays (for example, HD/1080p monitors). The `srcset` attribute adds a larger, high-DPI image for 2x displays (for example, most mobile devices, 4K desktop displays). Use high-resolution source images, as scaling a low-resolution image increases file size without improving quality.

### Create responsive layouts

Pixel density descriptors are used when the image has a fixed CSS size (e.g. a 960px product photo) and the viewport width doesn't matter. Here, you know exactly how many CSS pixels the image will be, and you want to provide higher-resolution versions for high-DPI screens.

However, if the image scales with the viewport — that is, its CSS size changes based on the screen width (for example, `width: 100%`, `width: 50vw`) — then use the width descriptor (`w`) instead to provide a range of widths:

```
<img  width="100%"  srcset="    /cdn-cgi/image/fit=contain,width=320/assets/hero.jpg   320w,    /cdn-cgi/image/fit=contain,width=640/assets/hero.jpg   640w,    /cdn-cgi/image/fit=contain,width=960/assets/hero.jpg   960w,    /cdn-cgi/image/fit=contain,width=1280/assets/hero.jpg 1280w,    /cdn-cgi/image/fit=contain,width=2560/assets/hero.jpg 2560w  "  src="/cdn-cgi/image/width=960/assets/hero.jpg"/>
```

The `w` values tell the browser the pixel width of each option. The browser factors in both viewport width and display density to choose the best match.

#### Use the `sizes` attribute

By default, the browser assumes the image fills the full viewport. If the image only occupies part of the screen, then you can use `sizes` to tell the browser how wide it actually is:

```
<!-- Image fills 50% of the viewport --><img style="width: 50vw" srcset="..." sizes="50vw" />
```

If the image can have a different size depending on media queries or other CSS properties (for example, `max-width`), then specify all the conditions in the `sizes` attribute:

```
<img  style="max-width: 640px"  srcset="    /cdn-cgi/image/fit=contain,width=320/assets/hero.jpg   320w,    /cdn-cgi/image/fit=contain,width=480/assets/hero.jpg   480w,    /cdn-cgi/image/fit=contain,width=640/assets/hero.jpg   640w,    /cdn-cgi/image/fit=contain,width=1280/assets/hero.jpg 1280w  "  sizes="(max-width: 640px) 100vw, 640px"/>
```

In the example above:

* If the screen size is below 640px, then the image fills the entire viewport.
* If the screen size is above 640px, then the image scales with the viewport and caps at 640px.
* On a 2x display above 640px, the browser needs 1280 physical pixels to fill the 640px layout width, so it selects the 1280w entry.

## Use `width=auto`

With `srcset`, you control exactly which sizes are available, which requires updating your HTML for every image.

On the other hand, `width=auto` takes a different approach, where Cloudflare determines the right width for each request from a single URL:

```
/cdn-cgi/image/width=auto/assets/hero.jpg
```

This is especially useful when optimizing remote images with [transformation flows](https://developers.cloudflare.com/images/optimization/transformations/flows/), where you can apply `width=auto` across your entire zone without modifying any markup.

When a request includes `width=auto`, Cloudflare determines the width based on screen size using client hints, if sent, or user-agent detection as a fallback.

### Client hints (preferred)

Browsers that support client hints (Chrome, Edge, Opera) send the viewport width in a request header. Then, Cloudflare reads this value and selects the right image size.

Rather than generating a unique image for every possible viewport width, Cloudflare snaps to the smallest breakpoint that is equal to or greater than the detected screen width.

The default breakpoints for client hints are: `320`, `768`, `960`, and `1200` pixels.

The following table shows the widths that Cloudflare will pick based on the default breakpoints. If the detected viewport width exceeds the largest breakpoint, the image is served at that largest breakpoint.

| Detected viewport width | Served image width |
| ----------------------- | ------------------ |
| 280px                   | 320px              |
| 500px                   | 768px              |
| 960px                   | 960px              |
| 1500px                  | 1200px             |

You can override the default breakpoints using the [wbreakpoints](https://developers.cloudflare.com/images/optimization/features/#width) sub-parameter, which accepts positive integers separated by semicolons.

#### Enabling client hints

Client hints give Cloudflare the most accurate information, but require opt-in from your site. Without them, `width=auto` falls back to user-agent detection.

You can enable client hints using one of the following methods:

**HTML `<meta>` tag**

Add the following in the `<head>` of your page before any other elements:

```
<meta  http-equiv="Delegate-CH"  content="sec-ch-dpr {ZONE}; sec-ch-viewport-width {ZONE}"/>
```

**HTTP response headers**

Add these headers to your HTML response:

```
critical-ch: sec-ch-viewport-width, sec-ch-dprpermissions-policy: ch-dpr=("{ZONE}"), ch-viewport-width=("{ZONE}")
```

### User-agent detection (fallback)

When client hints are not available, Cloudflare classifies the device as mobile or desktop based on the user-agent string and selects the corresponding size.

The default sizes for user-agent detection are:

| Device type                              | Default size |
| ---------------------------------------- | ------------ |
| Mobile (iPhone or Android in user-agent) | 768px        |
| Desktop (all other user-agents)          | 1200px       |

You can override the default sizes using the [wmobile and wdesktop](https://developers.cloudflare.com/images/optimization/features/#width) sub-parameters, which accept positive integers.

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/optimization/make-responsive-images/#page","headline":"Make responsive images · Cloudflare Images docs","description":"Automatically resize images for optimal display on every device.","url":"https://developers.cloudflare.com/images/optimization/make-responsive-images/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-05-26","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/optimization/","name":"Optimization"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/optimization/make-responsive-images/","name":"Make responsive images"}}]}
```

---

---
title: Control origin access
description: Hide original image sources and restrict access using Cloudflare Workers with image transformations.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Control origin access

You can serve resized images without giving access to the original image. Images can be hosted on another server outside of your zone, and the true source of the image can be entirely hidden. The origin server may require authentication to disclose the original image, without needing visitors to be aware of it. Access to the full-size image may be prevented by making it impossible to manipulate resizing parameters.

All these behaviors are completely customizable, because they are handled by custom code of a script running [on the edge in a Cloudflare Worker](https://developers.cloudflare.com/images/optimization/transformations/transform-via-workers/).

JavaScript

```
export default {  async fetch(request, env, ctx) {    // Here you can compute arbitrary imageURL and    // resizingOptions from any request data ...    return fetch(imageURL, { cf: { image: resizingOptions } });  },};
```

This code will be run for every request, but the source code will not be accessible to website visitors. This allows the code to perform security checks and contain secrets required to access the images in a controlled manner.

The examples below are only suggestions, and do not have to be followed exactly. You can compute image URLs and resizing options in many other ways.

Warning

When testing image transformations, make sure you deploy the script and test it from a regular web browser window. The preview in the dashboard does not simulate transformations.

## Hiding the image server

JavaScript

```
export default {  async fetch(request, env, ctx) {    const resizingOptions = {      /* resizing options will be demonstrated in the next example */    };
    const hiddenImageOrigin = "https://secret.example.com/hidden-directory";    const requestURL = new URL(request.url);    // Append the request path such as "/assets/image1.jpg" to the hiddenImageOrigin.    // You could also process the path to add or remove directories, modify filenames, etc.    const imageURL = hiddenImageOrigin + requestURL.pathname;    // This will fetch image from the given URL, but to the website's visitors this    // will appear as a response to the original request. Visitor’s browser will    // not see this URL.    return fetch(imageURL, { cf: { image: resizingOptions } });  },};
```

## Preventing access to full-size images

On top of protecting the original image URL, you can also validate that only certain image sizes are allowed:

JavaScript

```
export default {  async fetch(request, env, ctx) {  const imageURL = … // detail omitted in this example, see the previous example
  const requestURL = new URL(request.url)  const width = parseInt(requestURL.searchParams.get("width"), 10);  const resizingOptions = { width }  // If someone tries to manipulate your image URLs to reveal higher-resolution images,  // you can catch that and refuse to serve the request (or enforce a smaller size, etc.)  if (resizingOptions.width > 1000) {    return new Response("We don't allow viewing images larger than 1000 pixels wide", { status: 400 })  }  return fetch(imageURL, {cf:{image:resizingOptions}})},};
```

## Avoid image dimensions in URLs

You do not have to include actual pixel dimensions in the URL. You can embed sizes in the Worker script, and select the size in some other way — for example, by naming a preset in the URL:

JavaScript

```
export default {  async fetch(request, env, ctx) {    const requestURL = new URL(request.url);    const resizingOptions = {};
    // The regex selects the first path component after the "images"    // prefix, and the rest of the path (e.g. "/images/first/rest")    const match = requestURL.pathname.match(/images\/([^/]+)\/(.+)/);
    // You can require the first path component to be one of the    // predefined sizes only, and set actual dimensions accordingly.    switch (match && match[1]) {      case "small":        resizingOptions.width = 300;        break;      case "medium":        resizingOptions.width = 600;        break;      case "large":        resizingOptions.width = 900;        break;      default:        throw Error("invalid size");    }
    // The remainder of the path may be used to locate the original    // image, e.g. here "/images/small/image1.jpg" would map to    // "https://storage.example.com/bucket/image1.jpg" resized to 300px.    const imageURL = "https://storage.example.com/bucket/" + match[2];    return fetch(imageURL, { cf: { image: resizingOptions } });  },};
```

## Authenticated origin

Cloudflare image transformations cache resized images to aid performance. Images stored with restricted access are generally not recommended for resizing because sharing images customized for individual visitors is unsafe. However, in cases where the customer agrees to store such images in public cache, Cloudflare supports resizing images through Workers. At the moment, this is supported on authenticated AWS, Azure, Google Cloud, SecureAuth origins and origins behind Cloudflare Access.

JavaScript

```
// generate signed headers (application specific)const signedHeaders = generatedSignedHeaders();
fetch(private_url, {  headers: signedHeaders,  cf: {    image: {      format: "auto",      "origin-auth": "share-publicly",    },  },});
```

When using this code, the following headers are passed through to the origin, and allow your request to be successful:

* `Authorization`
* `Cookie`
* `x-amz-content-sha256`
* `x-amz-date`
* `x-ms-date`
* `x-ms-version`
* `x-sa-date`
* `cf-access-client-id`
* `cf-access-client-secret`

For more information, refer to:

* [AWS docs ↗](https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html)
* [Azure docs ↗](https://docs.microsoft.com/en-us/rest/api/storageservices/List-Containers2#request-headers)
* [Google Cloud docs ↗](https://cloud.google.com/storage/docs/aws-simple-migration)
* [Cloudflare Zero Trust docs](https://developers.cloudflare.com/cloudflare-one/access-controls/service-credentials/service-tokens/)
* [SecureAuth docs ↗](https://docs.secureauth.com/2104/en/authentication-api-guide.html)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/optimization/transformations/control-origin-access/#page","headline":"Control origin access · Cloudflare Images docs","description":"Hide original image sources and restrict access using Cloudflare Workers with image transformations.","url":"https://developers.cloudflare.com/images/optimization/transformations/control-origin-access/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-05-26","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/optimization/","name":"Optimization"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/optimization/transformations/","name":"Remote images (transformations)"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/optimization/transformations/control-origin-access/","name":"Control origin access"}}]}
```

---

---
title: Draw overlays and watermarks
description: Add watermarks, logos, and overlay images over other images using Workers.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Draw overlays and watermarks

Use [Workers](https://developers.cloudflare.com/workers/) to draw watermarks, logos, signatures over other images. Overlays support transparency, positioning, and compositing modes.

You can draw overlays in a Worker using two approaches:

* **[cf.image on a fetch subrequest](#draw-with-cfimage)** — Add a `draw` array to the image options. All images must be accessible via URL. Use this approach when optimizing images through the [URL interface](https://developers.cloudflare.com/images/optimization/features/#url-interface).
* **[Images binding](#draw-with-the-images-binding)** — Chain `.draw()` calls when overlaying images from any source, including [hosted images](https://developers.cloudflare.com/images/storage/binding/) or [R2](https://developers.cloudflare.com/r2/).

## Draw with `cf.image`

To draw overlays on a [fetch() subrequest](https://developers.cloudflare.com/images/optimization/transformations/transform-via-workers/) in Workers, you can add a `draw` array to your `cf.image` options.

Each entry in the array specifies an overlay and its options, including [optimization parameters](https://developers.cloudflare.com/images/optimization/features/) like [width](https://developers.cloudflare.com/images/optimization/features/#width--w), [height](https://developers.cloudflare.com/images/optimization/features/#height--h), [fit](https://developers.cloudflare.com/images/optimization/features/#fit), [blur](https://developers.cloudflare.com/images/optimization/features/#blur), and [rotate](https://developers.cloudflare.com/images/optimization/features/#rotate). Overlays are drawn in the order they appear — the last entry is the topmost layer.

JavaScript

```
export default {  async fetch(request) {    const imageURL = "https://example.com/image.png";
    return fetch(imageURL, {      cf: {        image: {          width: 800,          height: 600,          draw: [            {              url: "https://example.com/branding/logo.png",              bottom: 5,              right: 5,              fit: "contain",              width: 100,              height: 50,              opacity: 0.8,            },          ],        },      },    });  },};
```

## Draw with the Images binding

The [Images binding](https://developers.cloudflare.com/images/optimization/binding/) uses a chainable `.draw()` method to draw an overlay on top of an image. You can chain multiple `.draw()` calls for multiple overlays.

Pass the overlay image as the first argument, then the draw options as the second. To apply [optimization parameters](https://developers.cloudflare.com/images/optimization/features/) to the overlay image, pass an `.input()` chain with `.transform()` as the first argument.

JavaScript

```
export default {  async fetch(request, env) {    const img = await fetch("https://zzzdna.com/blue.png");    const watermark = await fetch("https://zzzdna.com/purple.png");
    const response = (      await env.IMAGES        .input(img.body)        .draw(          env.IMAGES.input(watermark.body).transform({ width: 100 }),          { bottom: 10, right: 10, opacity: 0.5 }        )        .output({ format: "image/avif" })    ).response();
    return response;  },};
```

## Options

The dimensions of the output image are always determined by the base image. The overlay is then drawn onto the base image's canvas.

The following draw-specific options can be used for positioning and blending.

### `url`

Absolute URL of the overlay image when drawing with `cf.image`. Supports any [supported image format](https://developers.cloudflare.com/images/get-started/limits/). For watermarks or non-rectangular overlays, use PNG or WebP images.

When drawing with the Images binding, the overlay is passed as image bytes or an `.input()` chain instead of a URL. Refer to [Draw with the Images binding](#draw-with-the-images-binding).

### `width` and `height`

Sets the maximum dimensions of the overlay image when drawing with `cf.image`. Accepts an integer (pixels) or a decimal between `0` and `1` representing a fraction of the base image's dimension. For example, `height:0.25` sets the overlay height to 25% of the height of the base image.

Use [fit](https://developers.cloudflare.com/images/optimization/features/#fit) and [gravity](https://developers.cloudflare.com/images/optimization/features/#gravity--g) to control how the overlay image is resized and cropped.

When drawing with the Images binding, the dimensions of the overlay image can be set using the `.transform()` method.

### `repeat`

Determines whether to tile the overlay across the image.

Accepts the following values:

* `true` — Tiles the overlay to cover the entire area. This is useful for watermarks.
* `x` — Tiles the overlay horizontally only.
* `y` — Tiles the overlay vertically only.

### `top`, `left`, `bottom`, `right`

Sets the position of the overlay image as an offset, in pixels, to the specified edge. `0` aligns the overlay flush to the edge. If no position is specified, then the overlay is centered.

For example, `{ bottom: 0, right: 10 }` places the overlay at the bottom-right corner, 10 pixels inward from the right edge.

Setting both `left` and `right`, or both `top` and `bottom` returns an error.

### `opacity`

Sets the opacity of the overlay image. Accepts a decimal value between `0.0` (fully transparent) and `1.0` (fully opaque). For example, `opacity: 0.5` makes the overlay semitransparent.

### `composite`

Controls how the overlay is blended with the base image using [Porter-Duff compositing operations ↗](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/feComposite). The default is `over`.

The composite mode only affects the area within the overlay's bounding box. The base image is always preserved outside of this area.

Accepts the following values:

#### `over`

Draws the overlay image on top of the base image. This is the default `composite` behavior.

The overlay covers the base where they overlap. Both images are visible where they do not overlap.

![composite=over output](https://developers.cloudflare.com/_astro/over.CJQByGnz.png) 

#### `in`

Shows the overlay only where the base image is opaque. If the overlay has transparent pixels that overlap with the base image, then those areas of the base image will also become transparent.

![composite=in output](https://developers.cloudflare.com/_astro/in.BaGoUETE.png) 

#### `atop`

Draws the overlay image on top of the base image, but only where the base image is opaque. This will clip the overlay image to the shape of the base image.

If the overlay has transparent pixels that overlap with the base image, then the base image remains visible (unlike `in`).

![composite=atop output](https://developers.cloudflare.com/_astro/atop.qvrwEw9r.png) 

#### `out`

Shows the overlay only where the base image is transparent. Within the overlay's bounding box, opaque areas of the base image will become transparent.

![composite=out output](https://developers.cloudflare.com/_astro/out.BU8dQJ0s.png) 

#### `xor`

Shows the areas of each image where the other is transparent. Overlapping opaque areas will become transparent. This can be used to create [rounded corners](https://developers.cloudflare.com/images/optimization/draw-overlays/#rounded-corners) or custom shapes.

![composite=xor output](https://developers.cloudflare.com/_astro/xor.6hxgorNh.png) 

#### `lighter`

Adds the color values of both images, which makes the overlapping areas brighter.

![composite=lighter output](https://developers.cloudflare.com/_astro/lighter.V0ahkzW6.png) 

## Examples

### Watermark

Tile a semitransparent watermark across the entire image using `cf.image`.

JavaScript

```
fetch(imageURL, {  cf: {    image: {      draw: [        {          url: "https://example.com/watermark.png",          repeat: true,          opacity: 0.2,        },      ],    },  },});
```

### Logo in the corner

Position a logo at the bottom-right corner using `cf.image`.

JavaScript

```
fetch(imageURL, {  cf: {    image: {      draw: [        {          url: "https://example.com/logo.png",          bottom: 5,          right: 5,        },      ],    },  },});
```

### Multiple overlays

Combine multiple overlays in one request using `cf.image`. They are drawn in order — the last entry is the topmost layer.

JavaScript

```
fetch(imageURL, {  cf: {    image: {      draw: [        { url: "https://example.com/watermark.png", repeat: true, opacity: 0.2 },        { url: "https://example.com/play-button.png" },        { url: "https://example.com/logo.png", bottom: 5, right: 5 },      ],    },  },});
```

### Rounded corners

Cut rounded corners from an image. A corner mask is drawn at each corner and rotated to match the position. `xor` removes the overlapping pixels.

The example below shows how this can be done using the [Images binding](https://developers.cloudflare.com/images/optimization/binding/).

TypeScript

```
const image = await fetch("https://example.com/photo.png");const mask = await fetch("https://example.com/corner-mask.png");
let [topLeft, topRight] = mask.body.tee();let bottomLeft, bottomRight;[topLeft, bottomLeft] = topLeft.tee();[topLeft, bottomRight] = topLeft.tee();
const output = await env.IMAGES  .input(image.body)  .draw(env.IMAGES.input(topLeft).transform({ rotate: 0 }), {    left: 0,    top: 0,    composite: "xor",  })  .draw(env.IMAGES.input(topRight).transform({ rotate: 90 }), {    right: 0,    top: 0,    composite: "xor",  })  .draw(env.IMAGES.input(bottomRight).transform({ rotate: 180 }), {    bottom: 0,    right: 0,    composite: "xor",  })  .draw(env.IMAGES.input(bottomLeft).transform({ rotate: 270 }), {    bottom: 0,    left: 0,    composite: "xor",  })  .output({ format: "image/png" });
return output.response();
```

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/optimization/draw-overlays/#page","headline":"Draw overlays and watermarks · Cloudflare Images docs","description":"Add watermarks, logos, and overlay images over other images using Workers.","url":"https://developers.cloudflare.com/images/optimization/draw-overlays/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-06-16","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/optimization/","name":"Optimization"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/optimization/draw-overlays/","name":"Draw overlays and watermarks"}}]}
```

---

---
title: Create transformation flows
description: Flows let you automatically apply image optimization to requests on your zone.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Create transformation flows

Define automated rules to optimize remote images without writing any code or changing your existing URLs.

Flows let you automatically apply image optimization to requests on your zone.

Each flow pairs a set of conditional triggers (for example, image is a JPEG or PNG) with optimization parameters (for example, transcode to AVIF).

When an image request matches a flow, Cloudflare transparently rewrites it through the Images service and serves the optimized result.

## Types of flows

You can use pre-built flows to handle migrations from other image optimization services (like Fastly) or create your own custom flows.

A **provider flow** is a translation layer that maps image URLs from another image optimization service to Cloudflare. Your existing URLs — including provider-specific parameters — continue to work without any changes.

Currently, Cloudflare supports flows for Fastly Image Optimizer. When enabled, Cloudflare automatically translates Fastly's parameters to their Cloudflare equivalents. For example:

* Fastly's `brightness` parameter accepts a range from `-100` to `100`, while Cloudflare's `brightness` works as a multiplier. The value is scaled accordingly.
* Fastly's `orient` parameter is mapped to Cloudflare's `flip` and `rotate` parameters.

A **custom flow** lets you define your own conditions and actions for image optimization.

This is well-suited for situations where you want to optimize your images broadly and consistently, such as:

* **Automatic format conversion** — Transcode all images to modern formats like AVIF or WebP across your entire site.
* **Responsive sizing** — Automatically resize images based on each user's device.
* **Directory-based optimization** — Enforce a consistent size for all images in a particular path, such as 100x100 for images where the path contains `/thumbnail`.

## How flows work

Before setting up a flow, make sure that transformations are turned on for your zone under **Images** \> **Transformations** in the [Cloudflare dashboard ↗](https://dash.cloudflare.com/?to=/:account/images/transformations).

When an image is requested on your zone, Cloudflare checks to see whether the request matches the conditions for any of your configured flows:

* Flows are evaluated from top to bottom in the order that they appear in the dashboard.
* If a request matches more than one flow, only the first matching flow will run.
* If no flow matches, then the request passes through to your origin unmodified.
* To control priority, you can reorder flows in the dashboard.

If the request matches a flow's conditions, then Cloudflare rewrites the URL to pass through the Images service with the specified parameters:

* A custom flow triggers only on requests for [supported image extensions](https://developers.cloudflare.com/images/get-started/limits/). HTML pages, CSS files, and other non-images are never affected.
* A provider flow evaluates requests based on provider-specific optimization parameters. For example, a Fastly provider flow triggers only when the request contains parameters like `?width`, `?height`, or `?fit`. Cloudflare will ignore any unrecognized parameters.

In your request lifecycle, flows are evaluated after standard HTTP [redirect rules](https://developers.cloudflare.com/rules/url-forwarding/):

* Any existing URL rewrites or redirect rules will be applied before Images evaluates the request, which may affect matching behavior.
* Flows include built-in loop prevention. If the request is already coming from the Images service, then the flow will not re-trigger on that subrequest.

## Set up a provider flow

Currently, Cloudflare supports flows to handle migrations from Fastly Image Optimizer.

To add a provider flow:

1. Log in to the [Cloudflare dashboard ↗](https://dash.cloudflare.com/) and select your account.
2. Go to **Images** \> **Transformations** and select your zone.
3. Select the **Automation** tab, then select **Add provider flow**.
4. Choose **Fastly** as the provider.
5. **Save** your flow.

## Set up a custom flow

### 1\. Create a new flow

In the Cloudflare dashboard, go to [**Images** \> **Transformations** ↗](https://dash.cloudflare.com/?to=/:account/images/transformations) and select the zone where you want to set up the custom flow.

Go to the **Automation** tab and select **Add custom flow** to open the side panel where you can configure your flow.

![Custom flow configuration panel](https://developers.cloudflare.com/_astro/custom-flow.DeAGR8BY_iGLSK.webp) 

### 2\. Configure the conditions

A custom flow is triggered when an incoming request matches all of the configured conditions in the flow:

* **File extension** — Match requests for all image formats or only specific file extensions, such as JPEG, PNG, or WebP.
* **URL path** — Match requests where the URL path matches a specified pattern, such as `/images/*` or `/assets/thumbnails/*`.
* **Query parameter** — Match requests where the query string contains a specified parameter, such as `orient`.

### 3\. Configure the actions

Next, define the optimization parameters that should be applied when the flow is triggered.

You can apply multiple actions within a single flow. For the full list of available parameters, refer to [Features](https://developers.cloudflare.com/images/optimization/features/).

The key parameters for most use cases are:

#### `format` | `f`

Set `format=auto` to automatically serve images in the most efficient format (e.g. AVIF, WebP) for each requesting browser.

If the browser doesn't support AVIF, then Cloudflare will fall back to WebP or a standard format.

Refer to [format](https://developers.cloudflare.com/images/optimization/features/#format)

#### `quality` | `q`

Control the compression quality of the output image. Accepts either:

* A **fixed value** from `1` (low quality, small file size) to `100` (high quality, large file size).
* A **perceptual quality level**: `high`, `medium-high`, `medium-low`, or `low`.

Refer to [quality](https://developers.cloudflare.com/images/optimization/features/#quality)

#### `slow-connection-quality` | `scq`

Override `quality` when a slow connection is detected via client hints. Accepts the same fixed or perceptual values as `quality`. This serves lower-quality (and smaller) images to users on slow networks without affecting users on fast connections.

Refer to [slow-connection-quality](https://developers.cloudflare.com/images/optimization/features/#slow-connection-quality)

#### `width` | `w`

Set [width=auto](https://developers.cloudflare.com/images/optimization/features/#width) to automatically size images based on the requesting device.

Cloudflare determines the optimal width using either [client hints](https://developers.cloudflare.com/images/optimization/make-responsive-images/#client-hints-preferred) (sent by the browser) or user-agent detection as a fallback.

You can fine-tune the `width=auto` behavior with the following sub-parameters:

| Sub-parameter | Description                                                                   | Default          |
| ------------- | ----------------------------------------------------------------------------- | ---------------- |
| wbreakpoints  | Override default breakpoint widths, in pixels (client hints)                  | 320;768;960;1200 |
| wmobile       | Override default width, in pixels, for mobile devices (user-agent detection)  | 768              |
| wdesktop      | Override default width, in pixels, for desktop devices (user-agent detection) | 1200             |

To learn how `width=auto` works, refer to our guide on [serving responsive images](https://developers.cloudflare.com/images/optimization/make-responsive-images/).

### 4\. Publish your flow

Select **Save** on the side panel to add your custom flow, then select **Save** on your list of flows to turn on your flow.

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/optimization/transformations/flows/#page","headline":"Create transformation flows · Cloudflare Images docs","description":"Flows let you automatically apply image optimization to requests on your zone.","url":"https://developers.cloudflare.com/images/optimization/transformations/flows/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-05-26","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/optimization/","name":"Optimization"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/optimization/transformations/","name":"Remote images (transformations)"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/optimization/transformations/flows/","name":"Create transformation flows"}}]}
```

---

---
title: Integrate with frameworks
description: Use Cloudflare Images transformations with Next.js and Nuxt image components.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Integrate with frameworks

## Next.js

Image transformations can be used automatically with the Next.js [<Image /> component ↗](https://nextjs.org/docs/api-reference/next/image).

To use image transformations, define a global image loader or multiple custom loaders for each `<Image />` component.

Next.js will request the image with the correct parameters for width and quality.

Image transformations will be responsible for caching and serving an optimal format to the client.

### Global Loader

To use Images with **all** your app's images, define a global [loaderFile ↗](https://nextjs.org/docs/pages/api-reference/components/image#loaderfile) for your app.

Add the following settings to the **next.config.js** file located at the root of your Next.js application.

TypeScript

```
module.exports = {  images: {    loader: "custom",    loaderFile: "./imageLoader.ts",  },};
```

Next, create the `imageLoader.ts` file in the specified path (relative to the root of your Next.js application).

TypeScript

```
import type { ImageLoaderProps } from "next/image";
const normalizeSrc = (src: string) => {  return src.startsWith("/") ? src.slice(1) : src;};
export default function cloudflareLoader({  src,  width,  quality,}: ImageLoaderProps) {  const params = [`width=${width}`];  if (quality) {    params.push(`quality=${quality}`);  }  if (process.env.NODE_ENV === "development") {    return `${src}?${params.join("&")}`;  }  return `/cdn-cgi/image/${params.join(",")}/${normalizeSrc(src)}`;}
```

### Custom Loaders

Alternatively, define a loader for each `<Image />` component.

JavaScript

```
import Image from "next/image";
const normalizeSrc = (src) => {  return src.startsWith("/") ? src.slice(1) : src;};
const cloudflareLoader = ({ src, width, quality }) => {  const params = [`width=${width}`];  if (quality) {    params.push(`quality=${quality}`);  }  if (process.env.NODE_ENV === "development") {    return `${src}?${params.join("&")}`;  }  return `/cdn-cgi/image/${params.join(",")}/${normalizeSrc(src)}`;};
const MyImage = (props) => {  return (    <Image      loader={cloudflareLoader}      src="/me.png"      alt="Picture of the author"      width={500}      height={500}      {...props}    />  );};
```

Note

For local development, you can enable [Resize images from any origin checkbox](https://developers.cloudflare.com/images/optimization/transformations/sources/) for your zone. Then, replace `/cdn-cgi/image/${paramsString}/${normalizeSrc(src)}` with an absolute URL path:

`https://<YOUR_DOMAIN.COM>/cdn-cgi/image/${paramsString}/${normalizeSrc(src)}`

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/optimization/transformations/integrate-with-frameworks/#page","headline":"Integrate with frameworks · Cloudflare Images docs","description":"Use Cloudflare Images transformations with Next.js and Nuxt image components.","url":"https://developers.cloudflare.com/images/optimization/transformations/integrate-with-frameworks/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/optimization/","name":"Optimization"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/optimization/transformations/","name":"Remote images (transformations)"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/optimization/transformations/integrate-with-frameworks/","name":"Integrate with frameworks"}}]}
```

---

---
title: Overview
description: Cloudflare Images transformations optimize and cache remote images from any origin at the edge.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Overview

Transformations are requests to optimize and manipulate remote images that are stored outside of Images.

When you ship applications on Cloudflare, you can use Images to automatically optimize and cache your images from any origin.

Our image optimization pipeline provides a rich set of [features](https://developers.cloudflare.com/images/optimization/features) that can be applied across entire media libraries to compress images at scale, transcode files into efficient formats for delivery, and resize and crop images for different use cases and devices.

## How it works

You can request transformations by using a specially-formatted URL to serve images on your Cloudflare zone or through Workers.

To serve transformations on your zone, you must first enable the feature:

1. In the [Cloudflare dashboard ↗](https://dash.cloudflare.com/?to=/:account/images/transformations), go to **Images** \> **Transformations**.
2. Select the zone where you want to serve transformations.
3. Enable **transformations** on your zone.

When the browser requests a transformed image, Cloudflare checks the edge cache for a previously optimized version with the same parameters:

**On a cache hit** — Cloudflare serves the optimized image directly from the edge without contacting the origin or re-applying the optimization parameters.

**On a cache miss** — Cloudflare fetches the original image from the source origin, applies the requested parameters (e.g. `format`, `width`, `quality`), caches the transformed result, and serves it to the browser. The original image is also cached to speed up future transformations of the same source.

Each unique combination of source image and parameters is cached and billed separately. The first request for each unique version within a calendar month is billed as one [unique transformation](https://developers.cloudflare.com/images/optimization/features), regardless of cache status. Subsequent requests for this transformation do not incur billable usage within the same calendar month.

## Configure your zone

After enabling transformations on your zone, you can configure how Cloudflare handles transformation requests:

* **[Define source origins](https://developers.cloudflare.com/images/optimization/transformations/sources)** — Specify which origins Cloudflare can pull source images from. By default, Cloudflare only accepts source images from the same zone where transformations are served.
* **[Create transformation flows](https://developers.cloudflare.com/images/optimization/transformations/flows)** — Set up automated rules that apply image optimization to matching requests without requiring URL changes or custom code.
* **[Control origin access](https://developers.cloudflare.com/images/optimization/transformations/control-origin-access)** — Use Workers to add custom logic for validating and controlling access to source images.
* **[Set up rewrite rules](https://developers.cloudflare.com/images/optimization/transformations/rewrite-rules)** — Use Transform Rules to rewrite image URLs and serve transformations from custom paths.

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/optimization/transformations/overview/#page","headline":"Overview · Cloudflare Images docs","description":"Cloudflare Images transformations optimize and cache remote images from any origin at the edge.","url":"https://developers.cloudflare.com/images/optimization/transformations/overview/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-05-26","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/optimization/","name":"Optimization"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/optimization/transformations/","name":"Remote images (transformations)"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/optimization/transformations/overview/","name":"Overview"}}]}
```

---

---
title: Preserve Content Credentials
description: Retain C2PA metadata and provenance data when transforming remote images with Cloudflare Images.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Preserve Content Credentials

[Content Credentials ↗](https://contentcredentials.org/) (or C2PA metadata) are a type of metadata that includes the full provenance chain of a digital asset. This provides information about an image's creation, authorship, and editing flow. This data is cryptographically authenticated and can be verified using an [open-source verification service ↗](https://contentcredentials.org/verify).

You can preserve Content Credentials when optimizing images stored in remote sources.

## Enable

You can configure how Content Credentials are handled for each zone where transformations are served.

In the Cloudflare dashboard under **Images** \> **Transformations**, navigate to a specific zone and enable the toggle to preserve Content Credentials:

![Enable Preserving Content Credentials in the dashboard](https://developers.cloudflare.com/_astro/preserve-content-credentials.BDptgOn0_ZPwgIT.webp) 

The behavior of this setting is determined by the [metadata](https://developers.cloudflare.com/images/optimization/features/#metadata) parameter for each transformation.

For example, if a transformation specifies `metadata=copyright`, then the EXIF copyright tag and all Content Credentials will be preserved in the resulting image and all other metadata will be discarded.

When Content Credentials are preserved in a transformation, Cloudflare will keep any existing Content Credentials embedded in the source image and automatically append and cryptographically sign additional actions.

When this setting is disabled, any existing Content Credentials will always be discarded.

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/optimization/transformations/preserve-content-credentials/#page","headline":"Preserve Content Credentials · Cloudflare Images docs","description":"Retain C2PA metadata and provenance data when transforming remote images with Cloudflare Images.","url":"https://developers.cloudflare.com/images/optimization/transformations/preserve-content-credentials/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-05-26","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/optimization/","name":"Optimization"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/optimization/transformations/","name":"Remote images (transformations)"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/optimization/transformations/preserve-content-credentials/","name":"Preserve Content Credentials"}}]}
```

---

---
title: Set up rewrite rules
description: Use Transform Rules to rewrite URLs for Cloudflare Images transformations and serve images from custom paths.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Set up rewrite rules

You can use Transform Rules to rewrite URLs for every image that you transform through Images.

This page covers examples for the following scenarios:

* Serve images from custom paths
* Modify existing URLs to be compatible with transformations in Images
* Transform every image requested on your zone with Images

To create a rule:

1. In the Cloudflare dashboard, go to the **Rules Overview** page.  
[ Go to **Overview** ](https://dash.cloudflare.com/?to=/:account/:zone/rules/overview)
2. Select **Create rule** next to **URL Rewrite Rules**.

## Before you start

Every rule runs before and after the transformation request.

If the path for the request matches the path where the original images are stored on your server, this may cause the request to fetch the original image to loop.

To direct the request to the origin server, you can check for the string `image-resizing` in the `Via` header:

`...and (not (any(http.request.headers["via"][*] contains "image-resizing")))`

## Serve images from custom paths

By default, requests to transform images through Images are served from the `/cdn-cgi/image/` path. You can use Transform Rules to rewrite URLs.

### Basic version

Free and Pro plans support string matching rules (including wildcard operations) that do not require regular expressions.

This example lets you rewrite a request from `example.com/images` to `example.com/cdn-cgi/image/`:

Text in Expression Editor

```
(starts_with(http.request.uri.path, "/images")) and (not (any(http.request.headers["via"][*] contains "image-resizing")))
```

Text in Path > Rewrite to > Dynamic

```
concat("/cdn-cgi/image", substring(http.request.uri.path, 7))
```

### Advanced version

Note

This feature requires a Business or Enterprise plan to enable regex in Transform Rules. Refer to [Cloudflare Transform Rules Availability](https://developers.cloudflare.com/rules/transform/#availability) for more information.

There is an advanced version of Transform Rules supporting regular expressions.

This example lets you rewrite a request from `example.com/images` to `example.com/cdn-cgi/image/`:

Text in Expression Editor

```
(http.request.uri.path matches "^/images/.*$") and (not (any(http.request.headers["via"][*] contains "image-resizing")))
```

Text in Path > Rewrite to > Dynamic

```
regex_replace(http.request.uri.path, "^/images/", "/cdn-cgi/image/")
```

## Modify existing URLs to be compatible with transformations in Images

Note

This feature requires a Business or Enterprise plan to enable regex in Transform Rules. Refer to [Cloudflare Transform Rules Availability](https://developers.cloudflare.com/rules/transform/#availability) for more information.

This example lets you rewrite your URL parameters to be compatible with Images:

```
(http.request.uri matches "^/(.*)\\?width=([0-9]+)&height=([0-9]+)$")
```

Text in Path > Rewrite to > Dynamic

```
regex_replace(  http.request.uri,  "^/(.*)\\?width=([0-9]+)&height=([0-9]+)$",  "/cdn-cgi/image/width=${2},height=${3}/${1}")
```

Leave the **Query** \> **Rewrite to** \> _Static_ field empty.

## Pass every image requested on your zone through Images

Note

This feature requires a Business or Enterprise plan to enable regular expressions in Transform Rules. Refer to [Cloudflare Transform Rules Availability](https://developers.cloudflare.com/rules/transform/#availability) for more information.

This example lets you transform every image that is requested on your zone with the `format=auto` option:

```
(http.request.uri.path.extension matches "(jpg)|(jpeg)|(png)|(gif)") and (not (any(http.request.headers["via"][*] contains "image-resizing")))
```

Text in Path > Rewrite to > Dynamic

```
regex_replace(http.request.uri.path, "/(.*)", "/cdn-cgi/image/format=auto/${1}")
```

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/optimization/transformations/rewrite-rules/#page","headline":"Serve images from custom paths · Cloudflare Images docs","description":"Use Transform Rules to rewrite URLs for Cloudflare Images transformations and serve images from custom paths.","url":"https://developers.cloudflare.com/images/optimization/transformations/rewrite-rules/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/optimization/","name":"Optimization"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/optimization/transformations/","name":"Remote images (transformations)"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/optimization/transformations/rewrite-rules/","name":"Set up rewrite rules"}}]}
```

---

---
title: Define source origins
description: Manage which origins Cloudflare Images can use as the source for image transformations.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Define source origins

When optimizing remote images, you can specify which origins can be used as the source for transformed images. By default, Cloudflare accepts only source images from the zone where your transformations are served.

On this page, you will learn how to define and manage the origins for the source images that you want to optimize.

Note

The allowed origins setting applies to requests from [Workers](https://developers.cloudflare.com/workers/).

If you use a Worker to optimize remote images via a `fetch()` subrequest, then this setting may conflict with existing logic that handles source images.

## How it works

In the Cloudflare dashboard, go to **Images** \> **Transformations** and select the zone where you want to serve transformations.

To get started, you must have [transformations enabled on your zone](https://developers.cloudflare.com/images/optimization/transformations/overview/#how-it-works).

In **Sources**, you can configure the origins for transformations on your zone.

![Enable allowed origins from the Cloudflare dashboard](https://developers.cloudflare.com/_astro/allowed-origins.4hu5lHws_ZsjEgI.webp) 

## Allow source images only from allowed origins

You can restrict source images to **allowed origins**, which applies transformations only to source images from a defined list.

By default, your accepted sources are set to **allowed origins**. Cloudflare will always allow source images from the same zone where your transformations are served.

If you request a transformation with a source image from outside your **allowed origins**, then the image will be rejected. For example, if you serve transformations on your zone `a.com` and do not define any additional origins, then `a.com/image.png` can be used as a source image, but `b.com/image.png` will return an error.

To define a new origin:

1. From **Sources**, select **Add origin**.
2. Under **Domain**, specify the domain for the source image. Only valid web URLs will be accepted.
![Add the origin for source images in the Cloudflare dashboard](https://developers.cloudflare.com/_astro/add-origin.BtfOyoOS_Z27sFtH.webp) 

When you add a root domain, subdomains are not accepted. In other words, if you add `b.com`, then source images from `media.b.com` will be rejected.

To support individual subdomains, define an additional origin such as `media.b.com`. If you add only `media.b.com` and not the root domain, then source images from the root domain (`b.com`) and other subdomains (`cdn.b.com`) will be rejected.

To support all subdomains, use the `*` wildcard at the beginning of the root domain. For example, `*.b.com` will accept source images from the root domain (like `b.com/image.png`) as well as from subdomains (like `media.b.com/image.png` or `cdn.b.com/image.png`).

1. Optionally, you can specify the **Path** for the source image. If no path is specified, then source images from all paths on this domain are accepted.

Cloudflare checks whether the defined path is at the beginning of the source path. If the defined path is not present at the beginning of the path, then the source image will be rejected.

For example, if you define an origin with domain `b.com` and path `/themes`, then `b.com/themes/image.png` will be accepted but `b.com/media/themes/image.png` will be rejected.

1. Select **Add**. Your origin will now appear in your list of allowed origins.
2. Select **Save**. These changes will take effect immediately.

When you configure **allowed origins**, only the initial URL of the source image is checked. Any redirects, including URLs that leave your zone, will be followed, and the resulting image will be transformed.

If you change your accepted sources to **any origin**, then your list of sources will be cleared and reset to default.

## Allow source images from any origin

When your accepted sources are set to **any origin**, any publicly available image can be used as the source image for transformations on this zone.

**Any origin** is less secure and may allow third parties to serve transformations on your zone.

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/optimization/transformations/sources/#page","headline":"Define source origins · Cloudflare Images docs","description":"Manage which origins Cloudflare Images can use as the source for image transformations.","url":"https://developers.cloudflare.com/images/optimization/transformations/sources/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/optimization/","name":"Optimization"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/optimization/transformations/","name":"Remote images (transformations)"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/optimization/transformations/sources/","name":"Define source origins"}}]}
```

---

---
title: Transform via fetch
description: Use cf.image options on a fetch() subrequest in a Worker to programmatically resize, format, and optimize remote images.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Transform via fetch

Workers lets you optimize images with a custom URL scheme.

You can set image optimization parameters on a `fetch()` subrequest in a Worker using the `cf.image` property. This is the same underlying mechanism as the [URL interface](https://developers.cloudflare.com/images/optimization/transformations/overview/#url-interface), but gives you programmatic control over every image request.

To work with image bytes directly instead of URLs, use the [Images binding](https://developers.cloudflare.com/images/optimization/binding/).

Here are a few examples of the flexibility that Workers give you:

* **Use a custom URL scheme**. Instead of specifying pixel dimensions in image URLs, use preset names such as `thumbnail` and `large`.
* **Hide the actual location of the original image**. You can store images in an external S3 bucket or a hidden folder on your server without exposing that information in URLs.
* **Implement content negotiation**. This is useful to adapt image sizes, formats and quality dynamically based on the device and condition of the network.

## How it works

The resizing feature is accessed via the [options](https://developers.cloudflare.com/workers/runtime-apis/request/#the-cf-property-requestinitcfproperties) of a `fetch()` [subrequest inside a Worker](https://developers.cloudflare.com/workers/runtime-apis/fetch/). The `fetch()` function accepts parameters in the second argument inside the `{cf: {image: {…}}}` object.

In your worker, where you would fetch the image using `fetch(request)`, add options like in the following example:

JavaScript

```
fetch(imageURL, {  cf: {    image: {      fit: "scale-down",      width: 800,      height: 600,    },  },});
```

These typings are also available in [our Workers TypeScript definitions library ↗](https://github.com/cloudflare/workers-types).

## Configure a Worker

Create a new script in the Workers section of the Cloudflare dashboard. Scope your Worker script to a path dedicated to serving assets, such as `/images/*` or `/assets/*`. Only supported image formats can be resized. Attempting to resize any other type of resource (CSS, HTML) will result in an error.

Warning

Do not set up the image optimization worker for the entire zone (`/*`). This will block all non-image requests and make your website inaccessible.

It is best to keep the path handled by the Worker separate from the path to original (unresized) images, to avoid request loops caused by the image resizing worker calling itself. For example, store your images in `example.com/originals/` directory, and handle resizing via `example.com/thumbnails/*` path that fetches images from the `/originals/` directory. If source images are stored in a location that is handled by a Worker, you must prevent the Worker from creating an infinite loop.

### Prevent request loops

To perform resizing and optimizations, the Worker must be able to fetch the original, unresized image from your origin server. If the path handled by your Worker overlaps with the path where images are stored on your server, it could cause an infinite loop by the Worker trying to request images from itself.

You must detect which requests must go directly to the origin server. When the `image-resizing` string is present in the `Via` header, it means that it is a request coming from another Worker and should be directed to the origin server:

JavaScript

```
export default {  async fetch(request) {    // If this request is coming from image resizing worker,    // avoid causing an infinite loop by resizing it again:    if (/image-resizing/.test(request.headers.get("via"))) {      return fetch(request);    }
    // Now you can safely use image resizing here  },};
```

## Lack of preview in the dashboard

Note

Image transformations are not simulated in the preview of the Workers dashboard editor.

The script preview of the Worker editor ignores `fetch()` options, and will always fetch unresized images. To see the effect of image transformations you must deploy the Worker script and use it outside of the editor.

## Error handling

When an image cannot be resized — for example, because the image does not exist or the resizing parameters were invalid — the response will have an HTTP status indicating an error (for example, `400`, `404`, or `502`).

By default, the error will be forwarded to the browser, but you can decide how to handle errors. For example, you can redirect the browser to the original, unresized image instead:

JavaScript

```
const response = await fetch(imageURL, options);
if (response.ok || response.redirected) {  // fetch() may respond with status 304  return response;} else {  return Response.redirect(imageURL, 307);}
```

Keep in mind that if the original images on your server are very large, it may be better not to display failing images at all, than to fall back to overly large images that could use too much bandwidth, memory, or break page layout.

You can also replace failed images with a placeholder image:

JavaScript

```
const response = await fetch(imageURL, options);if (response.ok || response.redirected) {  return response;} else {  // Change to a URL on your server  return fetch("https://img.example.com/blank-placeholder.png");}
```

## An example worker

Assuming you [set up a Worker](https://developers.cloudflare.com/workers/get-started/guide/) on `https://example.com/image-resizing` to handle URLs like `https://example.com/image-resizing?width=80&image=https://example.com/uploads/avatar1.jpg`:

JavaScript

```
/** * Fetch and log a request * @param {Request} request */export default {  async fetch(request) {    // Parse request URL to get access to query string    let url = new URL(request.url);
    // Cloudflare-specific options are in the cf object.    let options = { cf: { image: {} } };
    // Copy parameters from query string to request options.    // You can implement various different parameters here.    if (url.searchParams.has("fit"))      options.cf.image.fit = url.searchParams.get("fit");    if (url.searchParams.has("width"))      options.cf.image.width = parseInt(url.searchParams.get("width"), 10);    if (url.searchParams.has("height"))      options.cf.image.height = parseInt(url.searchParams.get("height"), 10);    if (url.searchParams.has("quality"))      options.cf.image.quality = parseInt(url.searchParams.get("quality"), 10);
    // Your Worker is responsible for automatic format negotiation. Check the Accept header.    const accept = request.headers.get("Accept");    if (/image\/avif/.test(accept)) {      options.cf.image.format = "avif";    } else if (/image\/webp/.test(accept)) {      options.cf.image.format = "webp";    }
    // Get URL of the original (full size) image to resize.    // You could adjust the URL here, e.g., prefix it with a fixed address of your server,    // so that user-visible URLs are shorter and cleaner.    const imageURL = url.searchParams.get("image");    if (!imageURL)      return new Response('Missing "image" value', { status: 400 });
    try {      // TODO: Customize validation logic      const { hostname, pathname } = new URL(imageURL);
      // Optionally, only allow URLs with JPEG, PNG, GIF, or WebP file extensions      // @see https://developers.cloudflare.com/images/url-format#supported-formats-and-limitations      if (!/\.(jpe?g|png|gif|webp)$/i.test(pathname)) {        return new Response("Disallowed file extension", { status: 400 });      }
      // Demo: Only accept "example.com" images      if (hostname !== "example.com") {        return new Response('Must use "example.com" source images', {          status: 403,        });      }    } catch (err) {      return new Response('Invalid "image" value', { status: 400 });    }
    // Build a request that passes through request headers    const imageRequest = new Request(imageURL, {      headers: request.headers,    });
    // Returning fetch() with resizing options will pass through response with the resized image.    return fetch(imageRequest, options);  },};
```

When testing image resizing, please deploy the script first. Resizing will not be active in the online editor in the dashboard.

## Warning about `cacheKey`

Resized images are always cached. They are cached as additional variants under a cache entry for the URL of the full-size source image in the `fetch` subrequest. Do not worry about using many different Workers or many external URLs — they do not influence caching of resized images, and you do not need to do anything for resized images to be cached correctly.

If you use the `cacheKey` fetch option to unify the caches of multiple source URLs, do not include any resizing options in the `cacheKey`. Doing so will fragment the cache and hurt caching performance. The `cacheKey` should reference only the full-size source image URL, not any of its resized versions.

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/optimization/transformations/transform-via-workers/#page","headline":"Transform via fetch · Cloudflare Images docs","description":"Use cf.image options on a fetch() subrequest in a Worker to programmatically resize, format, and optimize remote images.","url":"https://developers.cloudflare.com/images/optimization/transformations/transform-via-workers/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-06-10","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/optimization/","name":"Optimization"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/optimization/transformations/","name":"Remote images (transformations)"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/optimization/transformations/transform-via-workers/","name":"Transform via fetch"}}]}
```

---

---
title: Changelog
description: Recent changes and updates to Cloudflare Images.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Changelog

[ Subscribe to RSS ](https://developers.cloudflare.com/images/platform/changelog/index.xml)

## 2024-04-04

**Images upload widget**

Use the upload widget to integrate Cloudflare Images into your application by embedding the script into a static HTML page or installing a package that works with your preferred framework. To try out the upload widget, [sign up for the closed beta](https://forms.gle/vBu47y3638k8fkGF8).

## 2024-04-04

**Face cropping**

Crop and resize images of people's faces at scale using the existing gravity parameter and saliency detection, which sets the focal point of an image based on the most visually interesting pixels. To apply face cropping to your image optimization, [sign up for the closed beta](https://forms.gle/2bPbuijRoqGi6Qn36).

## 2024-01-15

**Cloudflare Images and Images Resizing merge**

Cloudflare Images and Images Resizing merged to create a more centralized and unified experience for Cloudflare Images. To learn more about the merge, refer to the [blog post](https://blog.cloudflare.com/merging-images-and-image-resizing/).

```json
{"@context":"https://schema.org","@type":"BlogPosting","@id":"https://developers.cloudflare.com/images/platform/changelog/#page","headline":"Changelog · Cloudflare Images docs","description":"Recent changes and updates to Cloudflare Images.","url":"https://developers.cloudflare.com/images/platform/changelog/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/platform/","name":"Platform"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/platform/changelog/","name":"Changelog"}}]}
```

---

---
title: Security
description: Protect Cloudflare Images optimization requests from abuse using WAF, Bot Management, and Rate Limiting.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Security

To further ensure the security and efficiency of image optimization services, you can adopt Cloudflare products that safeguard against malicious activities.

Cloudflare security products like [Cloudflare WAF](https://developers.cloudflare.com/waf/), [Cloudflare Bot Management](https://developers.cloudflare.com/bots/get-started/bot-management/) and [Cloudflare Rate Limiting](https://developers.cloudflare.com/waf/rate-limiting-rules/) can enhance the protection of your image optimization requests against abuse. This proactive approach ensures a reliable and efficient experience for all legitimate users.

```json
{"@context":"https://schema.org","@type":"WebPage","@id":"https://developers.cloudflare.com/images/reference/security/#page","headline":"Security · Cloudflare Images docs","description":"Protect Cloudflare Images optimization requests from abuse using WAF, Bot Management, and Rate Limiting.","url":"https://developers.cloudflare.com/images/reference/security/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/reference/","name":"Reference"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/reference/security/","name":"Security"}}]}
```

---

---
title: Troubleshooting
description: Diagnose and resolve common Cloudflare Images resizing errors, including error codes and origin configuration issues.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Troubleshooting

## Requests without resizing enabled

Does the response have a `Cf-Resized` header? If not, then resizing has not been attempted. Possible causes:

* The feature is not enabled in the Cloudflare Dashboard.
* There is another Worker running on the same request. Resizing is "forgotten" as soon as one Worker calls another. Do not use Workers scoped to the entire domain `/*`.
* Preview in the Editor in Cloudflare Dashboard does not simulate image resizing. You must deploy the Worker and test from another browser tab instead.

---

## Error responses from resizing

When resizing fails, the response body contains an error message explaining the reason, as well as the `Cf-Resized` header containing `err=code`:

* 9401 — The required arguments in `{cf:image{…}}` options are missing or are invalid. Try again. Refer to [Fetch options](https://developers.cloudflare.com/images/optimization/features/#parameters) for supported arguments.
* 9402 — The image was too large or the connection was interrupted. Refer to [Supported formats and limitations](https://developers.cloudflare.com/images/get-started/limits/) for more information.
* 9403 — A [request loop](https://developers.cloudflare.com/images/optimization/transformations/transform-via-workers/#prevent-request-loops) occurred because the image was already resized or the Worker fetched its own URL. Verify your Worker path and image path on the server do not overlap.
* 9406 & 9419 — The image URL is a non-HTTPS URL or the URL has spaces or unescaped Unicode. Check your URL and try again.
* 9407 — A lookup error occurred with the origin server's domain name. Check your DNS settings and try again.
* 9404 — The image does not exist on the origin server or the URL used to resize the image is wrong. Verify the image exists and check the URL.
* 9408 — The origin server returned an HTTP 4xx status code and may be denying access to the image. Confirm your image settings and try again.
* 9509 — The origin server returned an HTTP 5xx status code. This is most likely a problem with the origin server-side software, not the resizing.
* 9412 — The origin server returned a non-image, for example, an HTML page. This usually happens when an invalid URL is specified or server-side software has printed an error or presented a login page.
* 9413 — The image exceeds the maximum image area of 100 megapixels. Use a smaller image and try again.
* 9420 — The origin server redirected to an invalid URL. Confirm settings at your origin and try again.
* 9421 — The origin server redirected too many times. Confirm settings at your origin and try again.
* 9422 - The transformation request is rejected because the usage limit was reached. If you need to request more than 5,000 unique transformations, upgrade to an Images Paid plan.
* 9432 — The Images Binding is not available using legacy billing. Your account is using the legacy Image Resizing subscription. To bind Images to your Worker, you will need to update your plan to the Images subscription in the dashboard.
* 9504, 9505, & 9510 — The origin server could not be contacted because the origin server may be down or overloaded. Try again later.
* 9523 — The `/cdn-cgi/image/` resizing service could not perform resizing. This may happen when an image has invalid format. Use correctly formatted image and try again.
* 9524 — The `/cdn-cgi/image/` resizing service could not perform resizing. This may happen when an image URL is intercepted by a Worker. As an alternative you can [resize within the Worker](https://developers.cloudflare.com/images/optimization/transformations/transform-via-workers/). This can also happen when using a `pages.dev` URL of a [Cloudflare Pages](https://developers.cloudflare.com/pages/) project. In that case, you can use a [Custom Domain](https://developers.cloudflare.com/pages/configuration/custom-domains/) instead.
* 9520 — The image format is not supported. Refer to [Supported formats and limitations](https://developers.cloudflare.com/images/get-started/limits/) to learn about supported input and output formats.
* 9522 — The image exceeded the processing limit. This may happen briefly after purging an entire zone or when files with very large dimensions are requested. If the problem persists, contact support.
* 9529 - The image timed out while processing. This may happen when files with very large dimensions are requested or the server is overloaded.
* 9424, 9516, 9517, 9518 — Internal errors. Please contact support if you encounter these errors.

---

## Limits

These are the limits for images that are stored outside of Images:

* Maximum image size is 100 megapixels (for example, 10,000×10,000 pixels large). Maximum file size is 100 megabytes (MB). GIF/WebP animations are limited to 50 megapixels total (sum of sizes of all frames).
* [Bring Your Own IP (BYOIP)](https://developers.cloudflare.com/byoip/) is not compatible with Images when optimizing remote images (transformations).
* When [Polish](https://developers.cloudflare.com/images/polish/) can't optimize an image the Response Header `Warning: cf-images 299 "original is smaller"` is returned.

---

## Authorization and cookies are not supported

Image requests to the origin will be anonymized (no cookies, no auth, no custom headers). This is because we have to have one public cache for resized images, and it would be unsafe to share images that are personalized for individual visitors.

However, in cases where customers agree to store such images in public cache, Cloudflare supports resizing images through Workers [on authenticated origins](https://developers.cloudflare.com/images/optimization/transformations/transform-via-workers/).

---

## Caching and purging

Changes to image dimensions or other resizing options always take effect immediately — no purging necessary.

Image requests consists of two parts: running Worker code, and image processing. The Worker code is always executed and uncached. Results of image processing are cached for one hour or longer if origin server's `Cache-Control` header allows. Source image is cached using regular caching rules. Resizing follows redirects internally, so the redirects are cached too.

Because responses from Workers themselves are not cached at the edge, purging of _Worker URLs_ does nothing. Resized image variants are cached together under their source’s URL. When purging, use the (full-size) source image’s URL, rather than URLs of the Worker that requested resizing.

If the origin server sends an `Etag` HTTP header, the resized images will have an `Etag` HTTP header that has a format `cf-<gibberish>:<etag of the original image>`. You can compare the second part with the `Etag` header of the source image URL to check if the resized image is up to date.

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/reference/troubleshooting/#page","headline":"Troubleshooting · Cloudflare Images docs","description":"Diagnose and resolve common Cloudflare Images resizing errors, including error codes and origin configuration issues.","url":"https://developers.cloudflare.com/images/reference/troubleshooting/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/reference/","name":"Reference"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/reference/troubleshooting/","name":"Troubleshooting"}}]}
```

---

---
title: Manage hosted images with Workers
description: Use the Images binding to upload, list, retrieve, update, and delete hosted images from a Worker.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Manage hosted images with Workers

A [binding](https://developers.cloudflare.com/workers/runtime-apis/bindings/) connects your [Worker](https://developers.cloudflare.com/workers/) to external resources on the Developer Platform, like [Images](https://developers.cloudflare.com/images/), [R2 buckets](https://developers.cloudflare.com/r2/buckets/), or [KV namespaces](https://developers.cloudflare.com/kv/concepts/kv-namespaces/).

When managing hosted images, the Images binding lets your Worker upload, list, retrieve, update, and delete hosted images without calling the REST API directly. The `hosted` namespace exposes storage and management operations. This binding can also be used to [optimize hosted images](https://developers.cloudflare.com/images/optimization/binding/).

Bindings can be configured in the Cloudflare dashboard for your Worker or in the Wrangler configuration file in your project's directory.

Billing

Hosted image operations require a [paid Images plan with storage](https://developers.cloudflare.com/images/pricing/#images-paid). Calls count against your storage allowances in the same way as if you had used the REST API or the dashboard.

## Setup

To bind Images to your Worker, add the following to your Wrangler configuration file:

* [  wrangler.jsonc ](#tab-panel-9252)
* [  wrangler.toml ](#tab-panel-9253)

JSONC

```
{  "images": {    "binding": "IMAGES", // available in your Worker on env.IMAGES  },}
```

TOML

```
[images]binding = "IMAGES"
```

Within your Worker code, you can manage hosted images using the `env.IMAGES.hosted` namespace.

## Methods

The `env.IMAGES.hosted` namespace lets you upload and list images across your account. To manage a specific image, call `.image(imageId)` to get a handle, then call a method on it.

### `.upload(image, options)`

Uploads a new image to your account. You can pass image bytes as a stream or an `ArrayBuffer`. Returns [ImageMetadata](#imagemetadata).

Accepts the following options as an `ImageUploadOptions` object:

* `id` ` string ` — A custom ID to assign to the image. If omitted, Cloudflare generates a UUID. Refer to [Upload to a custom path](https://developers.cloudflare.com/images/storage/upload-images/upload-custom-path/).
* `filename` ` string ` — The filename to associate with the image.
* `requireSignedURLs` ` boolean ` — Sets whether the image should require a signed URL to view. Defaults to `false`.
* `metadata` ` Record<string, unknown> ` — Arbitrary metadata to store alongside the image.
* `creator` ` string ` — A user-defined identifier for the image creator.
* `encoding` ` 'base64' ` — Set to `base64` if the provided bytes are base64-encoded. The binding will decode them before upload.

### `.list(options)`

Lists images in your account with pagination. Returns [ImageList](#imagelist).

Accepts the following options as an `ImageListOptions` object:

* `limit` ` number ` — The maximum number of images to return in a page.
* `cursor` ` string ` — The continuation token returned by the previous `list()` call. Omit on the first page.
* `sortOrder` ` 'asc' | 'desc' ` — The order to sort results in by `uploaded` timestamp. Defaults to `asc`.
* `creator` ` string ` — Filter results to images uploaded with this creator identifier.

### `.image(imageId)`

Returns a handle for a single hosted image. The `imageId` can be the Cloudflare-generated UUID or a [custom ID](https://developers.cloudflare.com/images/storage/upload-images/upload-custom-path/).

The handle itself does not make a network request, so it is cheap to construct.

### `.image(imageId).details()`

Gets the metadata for an image. Returns [ImageMetadata](#imagemetadata) or `null` if no image with the given ID exists.

### `.image(imageId).bytes()`

Gets the raw bytes of an image. Returns `ReadableStream<Uint8Array>` or `null` if no image with the given ID exists. This streams the original uploaded file. Pass the image bytes to [.input()](https://developers.cloudflare.com/images/optimization/binding/) to optimize before serving, or use the URLs returned in [ImageMetadata.variants](#imagemetadata) or the [image delivery URL](https://developers.cloudflare.com/images/optimization/hosted-images/serve-uploaded-images/) to serve a predefined variant.

### `.image(imageId).update(options)`

Updates the metadata or access controls for an image. All fields are optional; only the specified fields will be changed. Returns [ImageMetadata](#imagemetadata) with the updated values.

Accepts the following options as an `ImageUpdateOptions` object:

* `requireSignedURLs` ` boolean ` — Whether signed URLs should be required to view the image. Cannot be set to `true` on an image that was uploaded with a [custom ID](https://developers.cloudflare.com/images/storage/upload-images/upload-custom-path/).
* `metadata` ` Record<string, unknown> ` — Replacement metadata for the image. This replaces the existing metadata rather than merging into it.
* `creator` ` string ` — A user-defined identifier for the image creator.

### `.image(imageId).delete()`

Deletes an image. Returns `true` if the image was deleted or `false` if no image with the given ID existed.

## Examples

### Upload an image from a request body

* [  JavaScript ](#tab-panel-9254)
* [  TypeScript ](#tab-panel-9255)

JavaScript

```
export default {  async fetch(request, env) {    if (!request.body) {      return new Response("Missing body", { status: 400 });    }
    const image = await env.IMAGES.hosted.upload(request.body, {      filename: "upload.jpg",      metadata: { source: "worker" },      requireSignedURLs: false,    });
    return Response.json(image);  },};
```

TypeScript

```
export default {  async fetch(request, env) {    if (!request.body) {      return new Response("Missing body", { status: 400 });    }
    const image = await env.IMAGES.hosted.upload(request.body, {      filename: "upload.jpg",      metadata: { source: "worker" },      requireSignedURLs: false,    });
    return Response.json(image);  },};
```

### Upload a base64-encoded image

Set `encoding: "base64"` and the binding will decode the body for you before uploading.

* [  JavaScript ](#tab-panel-9258)
* [  TypeScript ](#tab-panel-9259)

JavaScript

```
export default {  async fetch(request, env) {    if (!request.body) {      return new Response("Missing body", { status: 400 });    }
    const image = await env.IMAGES.hosted.upload(request.body, {      encoding: "base64",      filename: "upload.png",    });
    return Response.json(image);  },};
```

TypeScript

```
export default {  async fetch(request, env) {    if (!request.body) {      return new Response("Missing body", { status: 400 });    }
    const image = await env.IMAGES.hosted.upload(request.body, {      encoding: "base64",      filename: "upload.png",    });
    return Response.json(image);  },};
```

### List images with pagination

* [  JavaScript ](#tab-panel-9262)
* [  TypeScript ](#tab-panel-9263)

JavaScript

```
export default {  async fetch(request, env) {    let cursor;    const ids = [];
    do {      const page = await env.IMAGES.hosted.list({ limit: 100, cursor });      ids.push(...page.images.map((image) => image.id));      cursor = page.cursor;    } while (cursor);
    return Response.json({ count: ids.length, ids });  },};
```

TypeScript

```
export default {  async fetch(request, env) {    let cursor: string | undefined;    const ids: string[] = [];
    do {      const page = await env.IMAGES.hosted.list({ limit: 100, cursor });      ids.push(...page.images.map((image) => image.id));      cursor = page.cursor;    } while (cursor);
    return Response.json({ count: ids.length, ids });  },};
```

### Get the details for a single image

* [  JavaScript ](#tab-panel-9256)
* [  TypeScript ](#tab-panel-9257)

JavaScript

```
export default {  async fetch(request, env) {    const details = await env.IMAGES.hosted.image("IMAGE_ID").details();    if (!details) {      return new Response("Not found", { status: 404 });    }    return Response.json(details);  },};
```

TypeScript

```
export default {  async fetch(request, env) {    const details = await env.IMAGES.hosted.image("IMAGE_ID").details();    if (!details) {      return new Response("Not found", { status: 404 });    }    return Response.json(details);  },};
```

### Stream the original bytes for an image

* [  JavaScript ](#tab-panel-9260)
* [  TypeScript ](#tab-panel-9261)

JavaScript

```
export default {  async fetch(request, env) {    const bytes = await env.IMAGES.hosted.image("IMAGE_ID").bytes();    if (!bytes) {      return new Response("Not found", { status: 404 });    }    return new Response(bytes);  },};
```

TypeScript

```
export default {  async fetch(request, env) {    const bytes = await env.IMAGES.hosted.image("IMAGE_ID").bytes();    if (!bytes) {      return new Response("Not found", { status: 404 });    }    return new Response(bytes);  },};
```

### Update image metadata

* [  JavaScript ](#tab-panel-9264)
* [  TypeScript ](#tab-panel-9265)

JavaScript

```
export default {  async fetch(request, env) {    const updated = await env.IMAGES.hosted.image("IMAGE_ID").update({      metadata: { reviewed: true },    });    return Response.json(updated);  },};
```

TypeScript

```
export default {  async fetch(request, env) {    const updated = await env.IMAGES.hosted.image("IMAGE_ID").update({      metadata: { reviewed: true },    });    return Response.json(updated);  },};
```

### Delete an image

* [  JavaScript ](#tab-panel-9266)
* [  TypeScript ](#tab-panel-9267)

JavaScript

```
export default {  async fetch(request, env) {    const deleted = await env.IMAGES.hosted.image("IMAGE_ID").delete();    return new Response(deleted ? "Deleted" : "Not found", {      status: deleted ? 200 : 404,    });  },};
```

TypeScript

```
export default {  async fetch(request, env) {    const deleted = await env.IMAGES.hosted.image("IMAGE_ID").delete();    return new Response(deleted ? "Deleted" : "Not found", {      status: deleted ? 200 : 404,    });  },};
```

### Ingest a remote image into Images storage

This example fetches an image from a remote URL, uploads it into your Images account, and returns the first variant URL.

* [  JavaScript ](#tab-panel-9268)
* [  TypeScript ](#tab-panel-9269)

JavaScript

```
export default {  async fetch(request, env) {    const upstream = await fetch("https://example.com/photo.jpg");    if (!upstream.ok || !upstream.body) {      return new Response("Upstream fetch failed", { status: 502 });    }
    const image = await env.IMAGES.hosted.upload(upstream.body, {      filename: "photo.jpg",      metadata: { source: "example.com" },    });
    return Response.json({      id: image.id,      variant: image.variants[0],    });  },};
```

TypeScript

```
export default {  async fetch(request, env) {    const upstream = await fetch("https://example.com/photo.jpg");    if (!upstream.ok || !upstream.body) {      return new Response("Upstream fetch failed", { status: 502 });    }
    const image = await env.IMAGES.hosted.upload(upstream.body, {      filename: "photo.jpg",      metadata: { source: "example.com" },    });
    return Response.json({      id: image.id,      variant: image.variants[0],    });  },};
```

## Type definitions

### ImageMetadata

Returned by operations that retrieve, create, or update an image.

* `id` ` string `  
  * The unique identifier for the image.
* `filename` ` string ` optional  
  * The original filename supplied at upload time.
* `uploaded` ` string ` optional  
  * The date and time the image was uploaded, as an ISO 8601 string.
* `requireSignedURLs` ` boolean `  
  * Whether signed URLs are required to access this image. Refer to [Serve private images](https://developers.cloudflare.com/images/optimization/hosted-images/serve-private-images/).
* `meta` ` Record<string, unknown> ` optional  
  * User-supplied metadata associated with the image.
* `variants` ` Array<string> `  
  * Fully-formed URLs for each variant configured on your account. Refer to [Create variants](https://developers.cloudflare.com/images/optimization/hosted-images/create-variants/).
* `draft` ` boolean ` optional  
  * Whether the image is in a draft state (no bytes uploaded yet). Drafts are typically only seen on accounts using [Direct Creator Uploads](https://developers.cloudflare.com/images/storage/upload-images/direct-creator-upload/).
* `creator` ` string ` optional  
  * A user-defined identifier for the image creator.

### ImageList

Returned by [list()](#listoptions).

* `images` ` Array<ImageMetadata> `  
  * The images in this page of results.
* `cursor` ` string ` optional  
  * A continuation token to pass to the next `list()` call. Only present when there are more results.
* `listComplete` ` boolean `  
  * `true` when there are no further pages, `false` otherwise.

## Error handling

Methods that fail throw an `ImagesError` — `.upload()`, `.list()`, `.update()` — with the following properties:

* `code` ` number `  
  * A numeric error code that identifies the failure mode.
* `message` ` string `  
  * A human-readable description of the error.

Methods that fetch a single image — [.details()](#imageimageiddetails), [.bytes()](#imageimageidbytes), and [.delete()](#imageimageiddelete) — return `null` or `false` for "not found" rather than throwing.

You may want to wrap operations that can throw in a `try...catch` block.

## Local development

When you run `wrangler dev`, operations for managing hosted images are served by a local mock that stores images in an embedded KV namespace. The mock supports every method documented on this page, so you can develop and test your Worker offline.

The mock is only suitable for local development. To exercise the real Images service from your local environment, run `wrangler dev --remote`.

## Related resources

* [Optimize with Workers](https://developers.cloudflare.com/images/optimization/binding/) — Use the binding to optimize images from a Worker.
* [Upload via the REST API](https://developers.cloudflare.com/images/storage/upload-images/methods/) — The equivalent HTTP API.
* [Manage hosted images](https://developers.cloudflare.com/images/storage/manage-images/) — Dashboard and API workflows for managing stored images.

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/storage/binding/#page","headline":"Manage hosted images with Workers · Cloudflare Images docs","description":"Use the Images binding to upload, list, retrieve, update, and delete hosted images from a Worker.","url":"https://developers.cloudflare.com/images/storage/binding/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-06-10","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/storage/","name":"Storage"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/storage/binding/","name":"Manage hosted images with Workers"}}]}
```

---

---
title: Delete images
description: Remove images from Cloudflare Images storage using the dashboard or API.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Delete images

You can delete an image from the Cloudflare Images storage using the dashboard, the API, or from a Worker via the [Images binding](https://developers.cloudflare.com/images/storage/binding/#imageimageiddelete).

## Delete images via the Cloudflare dashboard

1. In the Cloudflare dashboard, go to the **Hosted Images** page.  
[ Go to **Hosted images** ](https://dash.cloudflare.com/?to=/:account/images/hosted)
2. Find the image you want to remove and select **Delete**.
3. (Optional) To delete more than one image, select the checkbox next to the images you want to delete and then **Delete selected**.

Your image will be deleted from your account.

## Delete images via the API

Make a `DELETE` request to the [delete image endpoint](https://developers.cloudflare.com/api/resources/images/subresources/v1/methods/delete/). `{image_id}` must be fully URL encoded in the API call URL.

Terminal window

```
curl --request DELETE https://api.cloudflare.com/client/v4/accounts/{account_id}/images/v1/{image_id} \--header "Authorization: Bearer <API_TOKEN>"
```

After the image has been deleted, the response returns `"success": true`.

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/storage/manage-images/delete-images/#page","headline":"Delete images · Cloudflare Images docs","description":"Remove images from Cloudflare Images storage using the dashboard or API.","url":"https://developers.cloudflare.com/images/storage/manage-images/delete-images/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-06-10","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/storage/","name":"Storage"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/storage/manage-images/","name":"Manage hosted images"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/storage/manage-images/delete-images/","name":"Delete images"}}]}
```

---

---
title: Edit images
description: Modify image settings in Cloudflare Images, including signed URL requirements and variant URLs.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Edit images

The Edit option provides you available options to modify a specific image. After choosing to edit an image, you can:

* Require signed URLs to use with that particular image.
* Use a cURL command you can use as an example to access the image.
* Use fully-formed URLs for all the variants configured in your account.

To edit an image:

1. In the Cloudflare dashboard, go to the **Hosted Images** page.  
[ Go to **Hosted images** ](https://dash.cloudflare.com/?to=/:account/images/hosted)
2. Locate the image you want to modify and select **Edit**.

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/storage/manage-images/edit-images/#page","headline":"Edit images · Cloudflare Images docs","description":"Modify image settings in Cloudflare Images, including signed URL requirements and variant URLs.","url":"https://developers.cloudflare.com/images/storage/manage-images/edit-images/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/storage/","name":"Storage"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/storage/manage-images/","name":"Manage hosted images"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/storage/manage-images/edit-images/","name":"Edit images"}}]}
```

---

---
title: Export images
description: Download the original version of images stored in Cloudflare Images via the dashboard or API.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Export images

Cloudflare Images supports image exports via the Cloudflare dashboard and API which allows you to get the original version of your image.

## Export images via the Cloudflare dashboard

1. In the Cloudflare dashboard, go to the **Hosted Images** page.  
[ Go to **Hosted images** ](https://dash.cloudflare.com/?to=/:account/images/hosted)
2. Find the image or images you want to export.
3. To export a single image, select **Export** from its menu. To export several images, select the checkbox next to each image and then select **Export selected**.

Your images are downloaded to your machine.

## Export images via the API

Make a `GET` request as shown in the example below. `<IMAGE_ID>` must be fully URL encoded in the API call URL.

`GET accounts/<ACCOUNT_ID>/images/v1/<IMAGE_ID>/blob`

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/storage/manage-images/export-images/#page","headline":"Export images · Cloudflare Images docs","description":"Download the original version of images stored in Cloudflare Images via the dashboard or API.","url":"https://developers.cloudflare.com/images/storage/manage-images/export-images/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/storage/","name":"Storage"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/storage/manage-images/","name":"Manage hosted images"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/storage/manage-images/export-images/","name":"Export images"}}]}
```

---

---
title: Configure webhooks
description: Set up webhooks to receive notifications when Cloudflare Images direct creator uploads succeed or fail.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Configure webhooks

Note

This feature is only available if your account has at least one zone with a pro plan or above. For more information, refer to our [plans ↗](https://www.cloudflare.com/plans/).

You can set up webhooks to receive notifications about your upload workflow. This will send an HTTP POST request to a specified endpoint when an image either successfully uploads or fails to upload.

Currently, webhooks are supported only for [direct creator uploads](https://developers.cloudflare.com/images/storage/upload-images/direct-creator-upload/).

To receive notifications for direct creator uploads:

1. In the Cloudflare dashboard, go to the **Notifications** pages.  
[ Go to **Notifications** ](https://dash.cloudflare.com/?to=/:account/notifications)
2. Select **Destinations**.
3. From the Webhooks card, select **Create**.
4. Enter information for your webhook and select **Save and Test**. The new webhook will appear in the **Webhooks** card and can be attached to notifications.
5. Next, go to **Notifications** \> **All Notifications** and select **Add**.
6. Under the list of products, locate **Images** and select **Select**.
7. Give your notification a name and optional description.
8. Under the **Webhooks** field, select the webhook that you recently created.
9. Select **Save**.

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/storage/upload-images/configure-webhooks/#page","headline":"Configure webhooks · Cloudflare Images docs","description":"Set up webhooks to receive notifications when Cloudflare Images direct creator uploads succeed or fail.","url":"https://developers.cloudflare.com/images/storage/upload-images/configure-webhooks/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-06-08","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/storage/","name":"Storage"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/storage/upload-images/","name":"Upload images"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/storage/upload-images/configure-webhooks/","name":"Configure webhooks"}}]}
```

---

---
title: Accept user-uploaded images
description: Use Cloudflare Images Direct Creator Upload to let users upload images with a one-time URL without exposing your API credentials.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Accept user-uploaded images

The Direct Creator Upload feature in Cloudflare Images lets your users upload images with a one-time upload URL without exposing your API key or token to the client. Using a direct creator upload also eliminates the need for an intermediary storage bucket and the storage/egress costs associated with it.

You can set up [webhooks](https://developers.cloudflare.com/images/storage/upload-images/configure-webhooks/) to receive notifications on your direct creator upload workflow.

## Request a one-time upload URL

Make a `POST` request to the `direct_upload` endpoint using the example below as reference.

Note

The `metadata` included in the request is never shared with end-users.

Terminal window

```
curl --request POST \https://api.cloudflare.com/client/v4/accounts/{account_id}/images/v2/direct_upload \--header "Authorization: Bearer <API_TOKEN>" \--form 'requireSignedURLs=true' \--form 'metadata={"key":"value"}'
```

After a successful request, you will receive a response similar to the example below. The `id` field is a future image identifier that will be uploaded by a creator.

```
{  "result": {    "id": "2cdc28f0-017a-49c4-9ed7-87056c83901",    "uploadURL": "https://upload.imagedelivery.net/Vi7wi5KSItxGFsWRG2Us6Q/2cdc28f0-017a-49c4-9ed7-87056c83901"  },  "result_info": null,  "success": true,  "errors": [],  "messages": []}
```

After calling the endpoint, a new draft image record is created, but the image will not appear in the list of images. If you want to check the status of the image record, you can make a request to the one-time upload URL using the `direct_upload` endpoint.

## Check the image record status

To check the status of a new draft image record, use the one-time upload URL as shown in the example below.

Terminal window

```
curl https://api.cloudflare.com/client/v4/accounts/{account_id}/images/v1/{image_id} \--header "Authorization: Bearer <API_TOKEN>"
```

After a successful request, you should receive a response similar to the example below. The `draft` field is set to `true` until a creator uploads an image. After an image is uploaded, the draft field is removed.

```
{  "result": {    "id": "2cdc28f0-017a-49c4-9ed7-87056c83901",    "metadata": {      "key": "value"    },    "uploaded": "2022-01-31T16:39:28.458Z",    "requireSignedURLs": true,    "variants": [      "https://imagedelivery.net/Vi7wi5KSItxGFsWRG2Us6Q/2cdc28f0-017a-49c4-9ed7-87056c83901/public",      "https://imagedelivery.net/Vi7wi5KSItxGFsWRG2Us6Q/2cdc28f0-017a-49c4-9ed7-87056c83901/thumbnail"    ],    "draft": true  },  "success": true,  "errors": [],  "messages": []}
```

The backend endpoint should return the `uploadURL` property to the client, which uploads the image without needing to pass any authentication information with it.

Below is an example of an HTML page that takes a one-time upload URL and uploads any image the user selects.

```
<!DOCTYPE html><html>  <body>    <form      action="INSERT_UPLOAD_URL_HERE"      method="post"      enctype="multipart/form-data"    >      <input type="file" id="myFile" name="file" />      <input type="submit" />    </form>  </body></html>
```

By default, the `uploadURL` expires after 30 minutes if unused. To override this option, add the following argument to the cURL command:

```
--data '{"expiry":"2021-09-14T16:00:00Z"}'
```

The expiry value must be a minimum of two minutes and maximum of six hours in the future.

## Direct Creator Upload with custom ID

You can specify a [custom ID](https://developers.cloudflare.com/images/storage/upload-images/upload-custom-path/) when you first request a one-time upload URL, instead of using the automatically generated ID for your image. Note that images with a custom ID cannot be made private with the [signed URL tokens](https://developers.cloudflare.com/images/optimization/hosted-images/serve-private-images/) feature (`--requireSignedURLs=true`).

To specify a custom ID, pass a form field with the name ID and corresponding custom ID value as shown in the example below.

```
--form 'id=this/is/my-customid'
```

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/storage/upload-images/direct-creator-upload/#page","headline":"Accept user-uploaded images · Cloudflare Images docs","description":"Use Cloudflare Images Direct Creator Upload to let users upload images with a one-time URL without exposing your API credentials.","url":"https://developers.cloudflare.com/images/storage/upload-images/direct-creator-upload/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/storage/","name":"Storage"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/storage/upload-images/","name":"Upload images"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/storage/upload-images/direct-creator-upload/","name":"Accept user-uploaded images"}}]}
```

---

---
title: Upload via batch API
description: Use the Cloudflare Images batch API to make sequential requests while bypassing global API rate limits.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Upload via batch API

The Images batch API lets you make several requests in sequence while bypassing Cloudflare’s global API rate limits.

To use the Images batch API, you will need to obtain a batch token and use the token to make several requests. The requests authorized by this batch token are made to a separate endpoint and do not count toward the global API rate limits. Each token is subject to a rate limit of 200 requests per second. You can use multiple tokens if you require higher throughput to the Cloudflare Images API.

To obtain a token, you can use the new `images/v1/batch_token` endpoint as shown in the example below.

Terminal window

```
curl "https://api.cloudflare.com/client/v4/accounts/{account_id}/images/v1/batch_token" \--header "Authorization: Bearer <API_TOKEN>"
# Response:{  "result": {    "token": "<BATCH_TOKEN>",    "expiresAt": "2023-08-09T15:33:56.273411222Z"  },  "success": true,  "errors": [],  "messages": []}
```

After getting your token, use it to make requests for:

* [Upload an image](https://developers.cloudflare.com/api/resources/images/subresources/v1/methods/create/) \- `POST /images/v1`
* [Delete an image](https://developers.cloudflare.com/api/resources/images/subresources/v1/methods/delete/) \- `DELETE /images/v1/{identifier}`
* [Image details](https://developers.cloudflare.com/api/resources/images/subresources/v1/methods/get/) \- `GET /images/v1/{identifier}`
* [Update image](https://developers.cloudflare.com/api/resources/images/subresources/v1/methods/edit/) \- `PATCH /images/v1/{identifier}`
* [List images V2](https://developers.cloudflare.com/api/resources/images/subresources/v2/methods/list/) \- `GET /images/v2`
* [Direct upload V2](https://developers.cloudflare.com/api/resources/images/subresources/v2/subresources/direct%5Fuploads/methods/create/) \- `POST /images/v2/direct_upload`

These options use a different host and a different path with the same method, request, and response bodies.

Request for list images V2 against api.cloudflare.com

```
curl "https://api.cloudflare.com/client/v4/accounts/{account_id}/images/v2" \--header "Authorization: Bearer <API_TOKEN>"
```

Example request using a batch token

```
curl "https://batch.imagedelivery.net/images/v1" \--header "Authorization: Bearer <BATCH_TOKEN>"
```

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/storage/upload-images/images-batch/#page","headline":"Upload via batch API · Cloudflare Images docs","description":"Use the Cloudflare Images batch API to make sequential requests while bypassing global API rate limits.","url":"https://developers.cloudflare.com/images/storage/upload-images/images-batch/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/storage/","name":"Storage"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/storage/upload-images/","name":"Upload images"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/storage/upload-images/images-batch/","name":"Upload via batch API"}}]}
```

---

---
title: Methods
description: Upload images to Cloudflare Images via the dashboard, API, or S3 import with Sourcing Kit.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Methods

Cloudflare gives you the option to [transform remote images](https://developers.cloudflare.com/images/optimization/transformations/overview), or upload into Images storage.

If you have a [paid Images plan](https://developers.cloudflare.com/images/pricing#images-paid), you can upload an image using the following methods:

* Upload directly through the dashboard. This is primarily used for one-off uploads.
* Upload using API endpoints.
* Upload from a Worker using the [Images binding](https://developers.cloudflare.com/images/storage/binding/).
* Import images from S3 using Sourcing Kit.

---

## Upload via dashboard

To upload an image from the dashboard, follow these steps:

1. Log in to the Cloudflare dashboard and select your account.
2. Go to the **Images & Stream** → **Hosted images** tab.
3. Drag and drop your image in the **Quick Upload** section. Alternatively, you can browse to select your image from your local disk.
4. When your image successfully uploads, your image will appear in the list of files.

## Upload using API

Upload, manage, and delete hosted images from the Images API.

The Images API endpoint uses the following format:

```
  https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/images/v1
```

When uploading through the API, you can use the following features:

* Upload an image from your local machine or from [a URL](https://developers.cloudflare.com/images/storage/upload-images/upload-url).
* Upload your image to a [custom ID path](https://developers.cloudflare.com/images/storage/upload-images/upload-custom-path/) rather than the path automatically generated by our Universal Unique Identifier (UUID).
* The [Direct Creator API](https://developers.cloudflare.com/images/storage/upload-images/direct-creator-upload/) lets you accept image uploads from your users without exposing your API token.
* The [Batch API](https://developers.cloudflare.com/images/storage/upload-images/images-batch/) returns a batch token that you can use to upload, manage, and delete images while bypassing Cloudflare’s global rate limits.

## Import from S3

Sourcing Kit is a data migration service that lets you copy objects from your Amazon S3 bucket to your Images storage.

With Sourcing Kit, you can:

* Define repositories of images to bulk import.
* Reuse existing sources and import only new images, skipping any other images that were already imported.
* Define target paths and prefixes for imported images.

Learn more about [Sourcing Kit](https://developers.cloudflare.com/images/storage/upload-images/sourcing-kit).

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/storage/upload-images/methods/#page","headline":"Methods · Cloudflare Images docs","description":"Upload images to Cloudflare Images via the dashboard, API, or S3 import with Sourcing Kit.","url":"https://developers.cloudflare.com/images/storage/upload-images/methods/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-06-10","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/storage/","name":"Storage"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/storage/upload-images/","name":"Upload images"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/storage/upload-images/methods/","name":"Methods"}}]}
```

---

---
title: Upload via Sourcing Kit
description: Bulk import images from Amazon S3 into Cloudflare Images using Sourcing Kit.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Upload via Sourcing Kit

With Sourcing Kit you can define one or multiple repositories of images to bulk import from Amazon S3\. Once you have these set up, you can reuse those sources and import only new images to your Cloudflare Images account. This helps you make sure that only usable images are imported, and skip any other objects or files that might exist in that source.

Sourcing Kit also lets you target paths, define prefixes for imported images, and obtain error logs for bulk operations.

## When to use Sourcing Kit

Sourcing Kit can be a good choice if the Amazon S3 bucket you are importing consists primarily of images stored using non-archival storage classes, as images stored using [archival storage classes ↗](https://aws.amazon.com/s3/storage-classes/#Archive) will be skipped and need to be imported separately. Specifically:

* Images stored using S3 Glacier tiers (not including Glacier Instant Retrieval) will be skipped and logged in the migration log.
* Images stored using S3 Intelligent Tiering and placed in Deep Archive tier will be skipped and logged in the migration log.

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/storage/upload-images/sourcing-kit/#page","headline":"Upload via Sourcing Kit · Cloudflare Images docs","description":"Bulk import images from Amazon S3 into Cloudflare Images using Sourcing Kit.","url":"https://developers.cloudflare.com/images/storage/upload-images/sourcing-kit/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/storage/","name":"Storage"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/storage/upload-images/","name":"Upload images"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/storage/upload-images/sourcing-kit/","name":"Upload via Sourcing Kit"}}]}
```

---

---
title: Credentials
description: Configure AWS IAM credentials to grant Cloudflare Images Sourcing Kit read access to your Amazon S3 bucket.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Credentials

To migrate images from Amazon S3, Sourcing Kit requires access permissions to your bucket. While you can use any AWS Identity and Access Management (IAM) user credentials with the correct permissions to create a Sourcing Kit source, Cloudflare recommends that you create a user with a narrow set of permissions.

To create the correct Sourcing Kit permissions:

1. Log in to your AWS IAM account.
2. Create a policy with the following format (replace `<BUCKET_NAME>` with the bucket you want to grant access to):  
```  
{  "Version": "2012-10-17",  "Statement": [    {      "Effect": "Allow",      "Action": ["s3:Get*", "s3:List*"],      "Resource": [        "arn:aws:s3:::<BUCKET_NAME>",        "arn:aws:s3:::<BUCKET_NAME>/*"      ]    }  ]}  
```
3. Next, create a new user and attach the created policy to that user.

You can now use both the Access Key ID and Secret Access Key to create a new source in Sourcing Kit. Refer to [Enable Sourcing Kit](https://developers.cloudflare.com/images/storage/upload-images/sourcing-kit/enable/) to learn more.

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/storage/upload-images/sourcing-kit/credentials/#page","headline":"Credentials · Cloudflare Images docs","description":"Configure AWS IAM credentials to grant Cloudflare Images Sourcing Kit read access to your Amazon S3 bucket.","url":"https://developers.cloudflare.com/images/storage/upload-images/sourcing-kit/credentials/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/storage/","name":"Storage"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/storage/upload-images/","name":"Upload images"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/storage/upload-images/sourcing-kit/","name":"Upload via Sourcing Kit"}},{"@type":"ListItem","position":6,"item":{"@id":"/images/storage/upload-images/sourcing-kit/credentials/","name":"Credentials"}}]}
```

---

---
title: Edit sources
description: Rename, delete, or abort import jobs for your Sourcing Kit sources in Cloudflare Images.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Edit sources

The Sourcing Kit main page has a list of all the import jobs and sources you have defined. This is where you can edit details for your sources or abort running import jobs.

## Source details

You can learn more about your sources by selecting the **Sources** tab on the Sourcing Kit dashboard. Use this option to rename or delete your sources.

1. In the Cloudflare dashboard, go to the **Hosted Images** page.  
[ Go to **Hosted images** ](https://dash.cloudflare.com/?to=/:account/images/hosted)
2. Select **Sourcing Kit**.
3. Select **Sources** and choose the source you want to change.
4. In this page you have the option to rename or delete your source. Select **Rename source** or **Delete source** depending on what you want to do.

## Abort import jobs

While Cloudflare Images is still running a job to import images into your account, you can abort it before it finishes.

1. In the Cloudflare dashboard, go to the **Hosted Images** page.  
[ Go to **Hosted images** ](https://dash.cloudflare.com/?to=/:account/images/hosted)
2. Select **Sourcing Kit**.
3. In **Imports** select the import job you want to abort.
4. The next page shows you a summary of the import. Select **Abort**.
5. Confirm that you want to abort your import job by selecting **Abort** on the dialog box.

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/storage/upload-images/sourcing-kit/edit/#page","headline":"Edit sources · Cloudflare Images docs","description":"Rename, delete, or abort import jobs for your Sourcing Kit sources in Cloudflare Images.","url":"https://developers.cloudflare.com/images/storage/upload-images/sourcing-kit/edit/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/storage/","name":"Storage"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/storage/upload-images/","name":"Upload images"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/storage/upload-images/sourcing-kit/","name":"Upload via Sourcing Kit"}},{"@type":"ListItem","position":6,"item":{"@id":"/images/storage/upload-images/sourcing-kit/edit/","name":"Edit sources"}}]}
```

---

---
title: Enable Sourcing Kit
description: Set up Sourcing Kit to create import jobs and start importing images from Amazon S3 into Cloudflare Images.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Enable Sourcing Kit

Enabling Sourcing Kit will set it up with the necessary information to start importing images from your Amazon S3 account.

## Create your first import job

1. In the Cloudflare dashboard, go to the **Hosted Images** page.  
[ Go to **Hosted images** ](https://dash.cloudflare.com/?to=/:account/images/hosted)
2. Select **Sourcing Kit**.
3. Select **Import images** to create an import job.
4. In **Source name** give your source an appropriate name.
5. In **Amazon S3 bucket information** enter the S3's bucket name where your images are stored.
6. In **Required credentials**, enter your Amazon S3 credentials. This is required to connect Cloudflare Images to your source and import your images. Refer to [Credentials](https://developers.cloudflare.com/images/storage/upload-images/sourcing-kit/credentials/) to learn more about how to set up credentials.
7. Select **Next**.
8. In **Basic rules** define the Amazon S3 path to import your images from, and the path you want to copy your images to in your Cloudflare Images account. This is optional, and you can leave these fields blank.
9. On the same page, in **Overwrite images**, you need to choose what happens when the files in your source change. The recommended action is to copy the new images and overwrite the old ones on your Cloudflare Images account. You can also choose to skip the import, and keep what you already have on your Cloudflare Images account.
10. Select **Next**.
11. Review and confirm the information regarding the import job you created. Select **Import images** to start importing images from your source.

Your import job is now created. You can review the job status on the Sourcing Kit main page. It will show you information such as how many objects it found, how many images were imported, and any errors that might have occurred.

Note

Sourcing Kit will warn you when you are about to reach the limit for your plan space quota. When you exhaust the space available in your plan, the importing jobs will be aborted. If you see this warning on Sourcing Kit’s main page, select **View plan** to change your plan’s limits.

## Define a new source

1. In the Cloudflare dashboard, go to the **Hosted Images** page.  
[ Go to **Hosted images** ](https://dash.cloudflare.com/?to=/:account/images/hosted)
2. Select **Sourcing Kit**.
3. Select **Import images** \> **Define a new source**.

Repeat steps 4-11 in [Create your first import job](#create-your-first-import-job) to finish setting up your new source.

## Define additional import jobs

You can have many import jobs from the same or different sources. If you select an existing source to create a new import job, you will not need to enter your credentials again.

1. In the Cloudflare dashboard, go to the **Hosted Images** page.  
[ Go to **Hosted images** ](https://dash.cloudflare.com/?to=/:account/images/hosted)
2. Select **Sourcing Kit**.
3. Select **Import images**.
4. Choose from one of the sources already configured.

Repeat steps 8-11 in [Create your first import job](#create-your-first-import-job) to finish setting up your new import job.

## Next steps

Refer to [Edit source details](https://developers.cloudflare.com/images/storage/upload-images/sourcing-kit/edit/) to learn more about editing details for import jobs you have already created, or to learn how to abort running import jobs.

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/storage/upload-images/sourcing-kit/enable/#page","headline":"Enable Sourcing Kit · Cloudflare Images docs","description":"Set up Sourcing Kit to create import jobs and start importing images from Amazon S3 into Cloudflare Images.","url":"https://developers.cloudflare.com/images/storage/upload-images/sourcing-kit/enable/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/storage/","name":"Storage"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/storage/upload-images/","name":"Upload images"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/storage/upload-images/sourcing-kit/","name":"Upload via Sourcing Kit"}},{"@type":"ListItem","position":6,"item":{"@id":"/images/storage/upload-images/sourcing-kit/enable/","name":"Enable Sourcing Kit"}}]}
```

---

---
title: Upload via custom path
description: Assign a custom ID path when uploading images to Cloudflare Images instead of using an auto-generated UUID.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Upload via custom path

You can use a custom ID path to upload an image instead of the path automatically generated by Cloudflare Images’ Universal Unique Identifier (UUID).

Custom paths support:

* Up to 1,024 characters.
* Any number of subpaths.
* The [UTF-8 encoding standard ↗](https://en.wikipedia.org/wiki/UTF-8) for characters.

Note

Images with custom ID paths cannot be made private using [signed URL tokens](https://developers.cloudflare.com/images/optimization/hosted-images/serve-private-images/). Additionally, when [serving images](https://developers.cloudflare.com/images/optimization/hosted-images/serve-uploaded-images/), any `%` characters present in Custom IDs must be encoded to `%25` in the image delivery URLs.

Make a `POST` request using the example below as reference. You can use custom ID paths when you upload via a URL or with a direct file upload.

Terminal window

```
curl --request POST https://api.cloudflare.com/client/v4/accounts/{account_id}/images/v1 \--header "Authorization: Bearer <API_TOKEN>" \--form 'url=https://<REMOTE_PATH_TO_IMAGE>' \--form 'id=<PATH_TO_YOUR_IMAGE>'
```

After successfully uploading the image, you will receive a response similar to the example below.

```
{  "result": {    "id": "<PATH_TO_YOUR_IMAGE>",    "filename": "<YOUR_IMAGE>",    "uploaded": "2022-04-20T09:51:09.559Z",    "requireSignedURLs": false,    "variants": [      "https://imagedelivery.net/Vi7wi5KSItxGFsWRG2Us6Q/<PATH_TO_YOUR_IMAGE>/public"    ]  },  "result_info": null,  "success": true,  "errors": [],  "messages": []}
```

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/storage/upload-images/upload-custom-path/#page","headline":"Upload via custom path · Cloudflare Images docs","description":"Assign a custom ID path when uploading images to Cloudflare Images instead of using an auto-generated UUID.","url":"https://developers.cloudflare.com/images/storage/upload-images/upload-custom-path/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/storage/","name":"Storage"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/storage/upload-images/","name":"Upload images"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/storage/upload-images/upload-custom-path/","name":"Upload via custom path"}}]}
```

---

---
title: Upload via a Worker
description: Learn how to upload images to Cloudflare using Workers. This guide provides code examples for uploading both standard and AI-generated images efficiently.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Upload via a Worker

You can use a Worker to upload your image to Cloudflare Images.

Refer to the example below or refer to the [Workers documentation](https://developers.cloudflare.com/workers/) for more information.

* [  JavaScript ](#tab-panel-9270)
* [  TypeScript ](#tab-panel-9271)

JavaScript

```
const API_URL =  "https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/images/v1";const TOKEN = "<YOUR_TOKEN_HERE>";
const image = await fetch("https://example.com/image.png");const bytes = await image.bytes();
const formData = new FormData();formData.append("file", new File([bytes], "image.png"));
const response = await fetch(API_URL, {  method: "POST",  headers: {    Authorization: `Bearer ${TOKEN}`,  },  body: formData,});
```

TypeScript

```
const API_URL =  "https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/images/v1";const TOKEN = "<YOUR_TOKEN_HERE>";
const image = await fetch("https://example.com/image.png");const bytes = await image.bytes();
const formData = new FormData();formData.append("file", new File([bytes], "image.png"));
const response = await fetch(API_URL, {  method: "POST",  headers: {    Authorization: `Bearer ${TOKEN}`,  },  body: formData,});
```

## Upload from AI generated images

You can use an AI Worker to generate an image and then upload that image to store it in Cloudflare Images. For more information about using Workers AI to generate an image, refer to the [SDXL-Lightning Model](https://developers.cloudflare.com/workers-ai/models/stable-diffusion-xl-lightning).

* [  JavaScript ](#tab-panel-9272)
* [  TypeScript ](#tab-panel-9273)

JavaScript

```
const API_URL =  "https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/images/v1";const TOKEN = "YOUR_TOKEN_HERE";
const stream = await env.AI.run("@cf/bytedance/stable-diffusion-xl-lightning", {  prompt: YOUR_PROMPT_HERE,});const bytes = await new Response(stream).bytes();
const formData = new FormData();formData.append("file", new File([bytes], "image.jpg"));
const response = await fetch(API_URL, {  method: "POST",  headers: {    Authorization: `Bearer ${TOKEN}`,  },  body: formData,});
```

TypeScript

```
const API_URL =  "https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/images/v1";const TOKEN = "YOUR_TOKEN_HERE";
const stream = await env.AI.run("@cf/bytedance/stable-diffusion-xl-lightning", {  prompt: YOUR_PROMPT_HERE,});const bytes = await new Response(stream).bytes();
const formData = new FormData();formData.append("file", new File([bytes], "image.jpg"));
const response = await fetch(API_URL, {  method: "POST",  headers: {    Authorization: `Bearer ${TOKEN}`,  },  body: formData,});
```

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/storage/upload-images/upload-file-worker/#page","headline":"Upload via a Worker · Cloudflare Images docs","description":"Learn how to upload images to Cloudflare using Workers. This guide provides code examples for uploading both standard and AI-generated images efficiently.","url":"https://developers.cloudflare.com/images/storage/upload-images/upload-file-worker/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/storage/","name":"Storage"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/storage/upload-images/","name":"Upload images"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/storage/upload-images/upload-file-worker/","name":"Upload via a Worker"}}]}
```

---

---
title: Upload via URL
description: Upload an image to Cloudflare Images by providing a source URL instead of a file.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Upload via URL

Before you upload an image, check the list of [supported formats and dimensions](https://developers.cloudflare.com/images/get-started/limits) to confirm your image will be accepted.

You can use the Images API to use a URL of an image instead of uploading the data.

Make a `POST` request using the example below as reference. Keep in mind that the `--form 'file=<FILE>'` and `--form 'url=<URL>'` fields are mutually exclusive.

Note

The `metadata` included in the request is never shared with end-users.

Terminal window

```
curl --request POST \https://api.cloudflare.com/client/v4/accounts/{account_id}/images/v1 \--header "Authorization: Bearer <API_TOKEN>" \--form 'url=https://[user:password@]example.com/<PATH_TO_IMAGE>' \--form 'metadata={"key":"value"}' \--form 'requireSignedURLs=false'
```

After successfully uploading the image, you will receive a response similar to the example below.

```
{  "result": {    "id": "2cdc28f0-017a-49c4-9ed7-87056c83901",    "filename": "image.jpeg",    "metadata": {      "key": "value"    },    "uploaded": "2022-01-31T16:39:28.458Z",    "requireSignedURLs": false,    "variants": [      "https://imagedelivery.net/Vi7wi5KSItxGFsWRG2Us6Q/2cdc28f0-017a-49c4-9ed7-87056c83901/public",      "https://imagedelivery.net/Vi7wi5KSItxGFsWRG2Us6Q/2cdc28f0-017a-49c4-9ed7-87056c83901/thumbnail"    ]  },  "success": true,  "errors": [],  "messages": []}
```

If your origin server returns an error while fetching the images, the API response will return a 4xx error.

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/storage/upload-images/upload-url/#page","headline":"Upload via URL · Cloudflare Images docs","description":"Upload an image to Cloudflare Images by providing a source URL instead of a file.","url":"https://developers.cloudflare.com/images/storage/upload-images/upload-url/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/storage/","name":"Storage"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/storage/upload-images/","name":"Upload images"}},{"@type":"ListItem","position":5,"item":{"@id":"/images/storage/upload-images/upload-url/","name":"Upload via URL"}}]}
```

---

---
title: Optimize mobile viewing
description: Lazy loading is an easy way to optimize the images on your webpages for mobile devices, with faster page load times and lower costs.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Optimize mobile viewing

You can use lazy loading to optimize the images on your webpages for mobile viewing. This helps address common challenges of mobile viewing, like slow network connections or weak processing capabilities.

Lazy loading has two main advantages:

* **Faster page load times** — Images are loaded as the user scrolls down the page, instead of all at once when the page is opened.
* **Lower costs for image delivery** — When using Cloudflare Images, you only pay to load images that the user actually sees. With lazy loading, images that are not scrolled into view do not count toward your billable Images requests.

Lazy loading is natively supported on all major browsers, including Chrome, Safari, Firefox, Opera, and Edge.

Note

If you use older methods, involving custom JavaScript or a JavaScript library, lazy loading may increase the initial load time of the page since the browser needs to download, parse, and execute JavaScript.

## Modify your loading attribute

Without modifying your loading attribute, most browsers will fetch all images on a page, prioritizing the images that are closest to the viewport by default. You can override this by modifying your `loading` attribute.

There are two possible `loading` attributes for your `<img>` tags: `lazy` and `eager`.

### Lazy loading

Lazy loading is recommended for most images. With Lazy loading, resources like images are deferred until they reach a certain distance from the viewport. If an image does not reach the threshold, then it does not get loaded.

Example of modifying the `loading` attribute of your `<img>` tags to be `"lazy"`:

```
<img src="example.com/cdn-cgi/width=300/image.png" loading="lazy" />
```

### Eager loading

If you have images that are in the viewport, eager loading, instead of lazy loading, is recommended. Eager loading loads the asset at the initial page load, regardless of its location on the page.

Example of modifying the `loading` attribute of your `<img>` tags to be `"eager"`:

```
<img src="example.com/cdn-cgi/width=300/image.png" loading="eager" />
```

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/tutorials/optimize-mobile-viewing/#page","headline":"Optimize mobile viewing · Cloudflare Images docs","description":"Lazy loading is an easy way to optimize the images on your webpages for mobile devices, with faster page load times and lower costs.","url":"https://developers.cloudflare.com/images/tutorials/optimize-mobile-viewing/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-05-05","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/tutorials/","name":"Tutorials"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/tutorials/optimize-mobile-viewing/","name":"Optimize mobile viewing"}}]}
```

---

---
title: Transform user-uploaded images before uploading to R2
description: Set up bindings to connect Images, R2, and Assets to your Worker
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/images/llms.txt  
> Use this file to discover all available pages before exploring further. 

[Skip to content](#%5Ftop) 

# Transform user-uploaded images before uploading to R2

In this guide, you will build an app that accepts image uploads, overlays the image with a visual watermark, then stores the transformed image in your R2 bucket.

---

With Images, you have the flexibility to choose where your original images are stored. You can transform images that are stored outside of the Images product, like in [R2](https://developers.cloudflare.com/r2/).

When you store user-uploaded media in R2, you may want to optimize or manipulate images before they are uploaded to your R2 bucket.

You will learn how to connect Developer Platform services to your Worker through bindings, as well as use various optimization features in the Images API.

## Prerequisites

Before you begin, you will need to do the following:

* Add an [Images Paid](https://developers.cloudflare.com/images/pricing/#images-paid) subscription to your account. This allows you to bind the Images API to your Worker.
* Create an [R2 bucket](https://developers.cloudflare.com/r2/get-started/), where the transformed images will be uploaded.
* Create a new Worker project.

If you are new, review how to [create your first Worker](https://developers.cloudflare.com/workers/get-started/guide/).

## 1: Set up your Worker project

To start, you will need to set up your project to use the following resources on the Developer Platform:

* [Images](https://developers.cloudflare.com/images/optimization/binding/) to transform, resize, and encode images directly from your Worker.
* [R2](https://developers.cloudflare.com/r2/api/workers/workers-api-usage/) to connect the bucket for storing transformed images.
* [Assets](https://developers.cloudflare.com/workers/static-assets/binding/) to access a static image that will be used as the visual watermark.

### Add the bindings to your Wrangler configuration

Configure your Wrangler configuration file to add the Images, R2, and Assets bindings:

* [  wrangler.jsonc ](#tab-panel-9274)
* [  wrangler.toml ](#tab-panel-9275)

JSONC

```
{  "images": {    "binding": "IMAGES"  },  "r2_buckets": [    {      "binding": "R2",      "bucket_name": "<BUCKET>"    }  ],  "assets": {    "directory": "./<DIRECTORY>",    "binding": "ASSETS"  }}
```

TOML

```
[images]binding = "IMAGES"
[[r2_buckets]]binding = "R2"bucket_name = "<BUCKET>"
[assets]directory = "./<DIRECTORY>"binding = "ASSETS"
```

Replace `<BUCKET>` with the name of the R2 bucket where you will upload the images after they are transformed. In your Worker code, you will be able to refer to this bucket using `env.R2.`

Replace `./<DIRECTORY>` with the name of the project's directory where the overlay image will be stored. In your Worker code, you will be able to refer to these assets using `env.ASSETS`.

### Set up your assets directory

Because we want to apply a visual watermark to every uploaded image, you need a place to store the overlay image.

The assets directory of your project lets you upload static assets as part of your Worker. When you deploy your project, these uploaded files, along with your Worker code, are deployed to Cloudflare's infrastructure in a single operation.

After you configure your Wrangler file, upload the overlay image to the specified directory. In our example app, the directory `./assets` contains the overlay image.

## 2: Build your frontend

You will need to build the interface for the app that lets users upload images.

In this example, the frontend is rendered directly from the Worker script.

To do this, make a new `html` variable, which contains a `form` element for accepting uploads. In `fetch`, construct a new `Response` with a `Content-Type: text/html` header to serve your static HTML site to the client:

* [  JavaScript ](#tab-panel-9278)
* [  TypeScript ](#tab-panel-9279)

JavaScript

```
const html = `<!DOCTYPE html>        <html>          <head>            <meta charset="UTF-8">            <title>Upload Image</title>          </head>          <body>            <h1>Upload an image</h1>            <form method="POST" enctype="multipart/form-data">              <input type="file" name="image" accept="image/*" required />              <button type="submit">Upload</button>            </form>          </body>        </html>`;
export default {  async fetch(request, env) {    if (request.method === "GET") {      return new Response(html, { headers: { "Content-Type": "text/html" } });    }    if (request.method === "POST") {      // This is called when the user submits the form    }  },};
```

TypeScript

```
const html = `<!DOCTYPE html>        <html>          <head>            <meta charset="UTF-8">            <title>Upload Image</title>          </head>          <body>            <h1>Upload an image</h1>            <form method="POST" enctype="multipart/form-data">              <input type="file" name="image" accept="image/*" required />              <button type="submit">Upload</button>            </form>          </body>        </html>`;
interface Env {  IMAGES: ImagesBinding;  R2: R2Bucket;  ASSETS: Fetcher;}
export default {  async fetch(request: Request, env: Env): Promise<Response> {    if (request.method === "GET") {      return new Response(html, { headers: { "Content-Type": "text/html" } });    }    if (request.method === "POST") {      // This is called when the user submits the form    }  },} satisfies ExportedHandler<Env>;
```

## 3: Read the uploaded image

After you have a `form`, you need to make sure you can transform the uploaded images.

Because the `form` lets users upload directly from their disk, you cannot use `fetch()` to get an image from a URL. Instead, you will operate on the body of the image as a stream of bytes.

To do this, parse the uploaded file from the `form` and get its stream:

* [  JavaScript ](#tab-panel-9276)
* [  TypeScript ](#tab-panel-9277)

JavaScript

```
export default {  async fetch(request, env) {    if (request.method === "GET") {      return new Response(html, { headers: { "Content-Type": "text/html" } });    }    if (request.method === "POST") {      try {        // Parse form data        const formData = await request.formData();        const file = formData.get("image");        if (!file || typeof file.stream !== "function") {          return new Response("No image file provided", { status: 400 });        }
        // Get uploaded image as a readable stream        const fileStream = file.stream();      } catch (err) {        console.log(err.message);      }    }  },};
```

TypeScript

```
export default {  async fetch(request: Request, env: Env): Promise<Response> {    if (request.method === "GET") {      return new Response(html, { headers: { "Content-Type": "text/html" } });    }    if (request.method === "POST") {      try {        // Parse form data        const formData = await request.formData();        const file = formData.get("image");        if (!file || typeof file.stream !== "function") {          return new Response("No image file provided", { status: 400 });        }
        // Get uploaded image as a readable stream        const fileStream = file.stream();      } catch (err) {        console.log((err as Error).message);      }    }  },} satisfies ExportedHandler<Env>;
```

Prevent potential errors when accessing request.body

The body of a [Request ↗](https://developer.mozilla.org/en-US/docs/Web/API/Request) can only be accessed once. If you previously used `request.formData()` in the same request, you may encounter a TypeError when attempting to access `request.body`.

To avoid errors, create a clone of the Request object with `request.clone()` for each subsequent attempt to access a Request's body. Keep in mind that Workers have a [memory limit of 128 MB per Worker](https://developers.cloudflare.com/workers/platform/limits/#memory) and loading particularly large files into a Worker's memory multiple times may reach this limit. To ensure memory usage does not reach this limit, consider using [Streams](https://developers.cloudflare.com/workers/runtime-apis/streams/).

## 4: Transform the image

For every uploaded image, you want to perform the following actions:

* Overlay the visual watermark that we added to our assets directory.
* Transcode the image — with its watermark — to `AVIF`. This compresses the image and reduces its file size.
* Upload the transformed image to R2.

### Set up the overlay image

To fetch the overlay image from the assets directory, create a function `assetUrl` then use `env.ASSETS` to retrieve the `watermark.png` image:

* [  JavaScript ](#tab-panel-9280)
* [  TypeScript ](#tab-panel-9281)

JavaScript

```
function assetUrl(request, path) {  const url = new URL(request.url);  url.pathname = path;  return url;}
export default {  async fetch(request, env) {    if (request.method === "GET") {      return new Response(html, { headers: { "Content-Type": "text/html" } });    }    if (request.method === "POST") {      try {        // Parse form data        const formData = await request.formData();        const file = formData.get("image");        if (!file || typeof file.stream !== "function") {          return new Response("No image file provided", { status: 400 });        }
        // Get uploaded image as a readable stream        const fileStream = file.stream();
        // Fetch image as watermark        const watermarkResponse = await env.ASSETS.fetch(          assetUrl(request, "watermark.png"),        );        const watermarkStream = watermarkResponse.body;      } catch (err) {        console.log(err.message);      }    }  },};
```

TypeScript

```
function assetUrl(request: Request, path: string): URL {  const url = new URL(request.url);  url.pathname = path;  return url;}
export default {  async fetch(request: Request, env: Env): Promise<Response> {    if (request.method === "GET") {      return new Response(html, { headers: { "Content-Type": "text/html" } });    }    if (request.method === "POST") {      try {        // Parse form data        const formData = await request.formData();        const file = formData.get("image");        if (!file || typeof file.stream !== "function") {          return new Response("No image file provided", { status: 400 });        }
        // Get uploaded image as a readable stream        const fileStream = file.stream();
        // Fetch image as watermark        const watermarkResponse = await env.ASSETS.fetch(          assetUrl(request, "watermark.png"),        );        const watermarkStream = watermarkResponse.body;      } catch (err) {        console.log((err as Error).message);      }    }  },} satisfies ExportedHandler<Env>;
```

### Watermark and transcode the image

You can interact with the Images binding through `env.IMAGES`.

This is where you will put all of the optimization operations you want to perform on the image. Here, you will use the `.draw()` function to apply a visual watermark over the uploaded image, then use `.output()` to encode the image as AVIF:

* [  JavaScript ](#tab-panel-9282)
* [  TypeScript ](#tab-panel-9283)

JavaScript

```
function assetUrl(request, path) {  const url = new URL(request.url);  url.pathname = path;  return url;}
export default {  async fetch(request, env) {    if (request.method === "GET") {      return new Response(html, { headers: { "Content-Type": "text/html" } });    }    if (request.method === "POST") {      try {        // Parse form data        const formData = await request.formData();        const file = formData.get("image");        if (!file || typeof file.stream !== "function") {          return new Response("No image file provided", { status: 400 });        }
        // Get uploaded image as a readable stream        const fileStream = file.stream();
        // Fetch image as watermark        const watermarkResponse = await env.ASSETS.fetch(          assetUrl(request, "watermark.png"),        );        const watermarkStream = watermarkResponse.body;        if (!watermarkStream) {          return new Response("Failed to fetch watermark", { status: 500 });        }
        // Apply watermark and convert to AVIF        const imageResponse = (          await env.IMAGES.input(fileStream)            // Draw the watermark on top of the image            .draw(              env.IMAGES.input(watermarkStream).transform({                width: 100,                height: 100,              }),              { bottom: 10, right: 10, opacity: 0.75 },            )            // Output the final image as AVIF            .output({ format: "image/avif" })        ).response();      } catch (err) {        console.log(err.message);      }    }  },};
```

TypeScript

```
function assetUrl(request: Request, path: string): URL {  const url = new URL(request.url);  url.pathname = path;  return url;}
export default {  async fetch(request: Request, env: Env): Promise<Response> {    if (request.method === "GET") {      return new Response(html, { headers: { "Content-Type": "text/html" } });    }    if (request.method === "POST") {      try {        // Parse form data        const formData = await request.formData();        const file = formData.get("image");        if (!file || typeof file.stream !== "function") {          return new Response("No image file provided", { status: 400 });        }
        // Get uploaded image as a readable stream        const fileStream = file.stream();
        // Fetch image as watermark        const watermarkResponse = await env.ASSETS.fetch(          assetUrl(request, "watermark.png"),        );        const watermarkStream = watermarkResponse.body;        if (!watermarkStream) {          return new Response("Failed to fetch watermark", { status: 500 });        }
        // Apply watermark and convert to AVIF        const imageResponse = (          await env.IMAGES.input(fileStream)            // Draw the watermark on top of the image            .draw(              env.IMAGES.input(watermarkStream).transform({                width: 100,                height: 100,              }),              { bottom: 10, right: 10, opacity: 0.75 },            )            // Output the final image as AVIF            .output({ format: "image/avif" })        ).response();      } catch (err) {        console.log((err as Error).message);      }    }  },} satisfies ExportedHandler<Env>;
```

## 5: Upload to R2

Upload the transformed image to R2.

By creating a `fileName` variable, you can specify the name of the transformed image. In this example, you append the date to the name of the original image before uploading to R2.

Here is the full code for the example:

* [  JavaScript ](#tab-panel-9284)
* [  TypeScript ](#tab-panel-9285)

JavaScript

```
const html = `<!DOCTYPE html>        <html>          <head>            <meta charset="UTF-8">            <title>Upload Image</title>          </head>          <body>            <h1>Upload an image</h1>            <form method="POST" enctype="multipart/form-data">              <input type="file" name="image" accept="image/*" required />              <button type="submit">Upload</button>            </form>          </body>        </html>`;
function assetUrl(request, path) {  const url = new URL(request.url);  url.pathname = path;  return url;}
export default {  async fetch(request, env) {    if (request.method === "GET") {      return new Response(html, { headers: { "Content-Type": "text/html" } });    }    if (request.method === "POST") {      try {        // Parse form data        const formData = await request.formData();        const file = formData.get("image");        if (!file || typeof file.stream !== "function") {          return new Response("No image file provided", { status: 400 });        }
        // Get uploaded image as a readable stream        const fileStream = file.stream();
        // Fetch image as watermark        const watermarkResponse = await env.ASSETS.fetch(          assetUrl(request, "watermark.png"),        );        const watermarkStream = watermarkResponse.body;        if (!watermarkStream) {          return new Response("Failed to fetch watermark", { status: 500 });        }
        // Apply watermark and convert to AVIF        const imageResponse = (          await env.IMAGES.input(fileStream)            // Draw the watermark on top of the image            .draw(              env.IMAGES.input(watermarkStream).transform({                width: 100,                height: 100,              }),              { bottom: 10, right: 10, opacity: 0.75 },            )            // Output the final image as AVIF            .output({ format: "image/avif" })        ).response();
        // Add timestamp to file name        const fileName = `image-${Date.now()}.avif`;
        // Upload to R2        await env.R2.put(fileName, imageResponse.body);
        return new Response(`Image uploaded successfully as ${fileName}`, {          status: 200,        });      } catch (err) {        console.log(err.message);        return new Response("Internal error", { status: 500 });      }    }    return new Response("Method not allowed", { status: 405 });  },};
```

TypeScript

```
interface Env {  IMAGES: ImagesBinding;  R2: R2Bucket;  ASSETS: Fetcher;}
const html = `<!DOCTYPE html>        <html>          <head>            <meta charset="UTF-8">            <title>Upload Image</title>          </head>          <body>            <h1>Upload an image</h1>            <form method="POST" enctype="multipart/form-data">              <input type="file" name="image" accept="image/*" required />              <button type="submit">Upload</button>            </form>          </body>        </html>`;
function assetUrl(request: Request, path: string): URL {  const url = new URL(request.url);  url.pathname = path;  return url;}
export default {  async fetch(request: Request, env: Env): Promise<Response> {    if (request.method === "GET") {      return new Response(html, { headers: { "Content-Type": "text/html" } });    }    if (request.method === "POST") {      try {        // Parse form data        const formData = await request.formData();        const file = formData.get("image");        if (!file || typeof file.stream !== "function") {          return new Response("No image file provided", { status: 400 });        }
        // Get uploaded image as a readable stream        const fileStream = file.stream();
        // Fetch image as watermark        const watermarkResponse = await env.ASSETS.fetch(          assetUrl(request, "watermark.png"),        );        const watermarkStream = watermarkResponse.body;        if (!watermarkStream) {          return new Response("Failed to fetch watermark", { status: 500 });        }
        // Apply watermark and convert to AVIF        const imageResponse = (          await env.IMAGES.input(fileStream)            // Draw the watermark on top of the image            .draw(              env.IMAGES.input(watermarkStream).transform({                width: 100,                height: 100,              }),              { bottom: 10, right: 10, opacity: 0.75 },            )            // Output the final image as AVIF            .output({ format: "image/avif" })        ).response();
        // Add timestamp to file name        const fileName = `image-${Date.now()}.avif`;
        // Upload to R2        await env.R2.put(fileName, imageResponse.body);
        return new Response(`Image uploaded successfully as ${fileName}`, {          status: 200,        });      } catch (err) {        console.log((err as Error).message);        return new Response("Internal error", { status: 500 });      }    }    return new Response("Method not allowed", { status: 405 });  },} satisfies ExportedHandler<Env>;
```

## Next steps

In this tutorial, you learned how to connect your Worker to various resources on the Developer Platform to build an app that accepts image uploads, transform images, and uploads the output to R2.

Next, you can [set up a transformation URL](https://developers.cloudflare.com/images/optimization/features/#url-interface) to dynamically optimize images that are stored in R2.

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/images/tutorials/optimize-user-uploaded-image/#page","headline":"Transform user-uploaded images before uploading to R2 · Cloudflare Images docs","description":"Set up bindings to connect Images, R2, and Assets to your Worker","url":"https://developers.cloudflare.com/images/tutorials/optimize-user-uploaded-image/","inLanguage":"en","image":"https://developers.cloudflare.com/dev-products-preview.png","dateModified":"2026-06-10","publisher":{"@type":"Organization","name":"Cloudflare","url":"https://www.cloudflare.com/"},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/images/","name":"Cloudflare Images"}},{"@type":"ListItem","position":3,"item":{"@id":"/images/tutorials/","name":"Tutorials"}},{"@type":"ListItem","position":4,"item":{"@id":"/images/tutorials/optimize-user-uploaded-image/","name":"Transform user-uploaded images before uploading to R2"}}]}
```
