Cloudflare Images: Comprehensive Guide

Updated: 2026-04-06

Who this guide is for

This guide is for engineers, architects, and product teams who want to understand Cloudflare Images in enough detail to choose the right architecture, implement it safely, and avoid the most common mistakes.

It covers:


1. What Cloudflare Images is

Cloudflare Images is Cloudflare’s image platform for storing, transforming, optimizing, and delivering images on Cloudflare’s global network.

A key point that confuses many teams: Cloudflare Images is really two related products under one name:

  1. Hosted Images
    • You upload images into Cloudflare Images storage.
    • Cloudflare stores the originals.
    • You deliver named variants or flexible variants from Cloudflare.
  2. Transformations
    • Your original images stay somewhere else, such as your origin, R2, or another public source.
    • Cloudflare transforms and caches them at delivery time.
    • You are billed by unique transformations, not by Cloudflare-hosted image storage.

That split determines your architecture, pricing, security model, and migration path.


2. Quick decision guide

flowchart TD
    A[Need an image pipeline] --> B{Do you want Cloudflare to store originals?}
    B -->|Yes| C[Use Hosted Images]
    B -->|No| D{Are originals already in R2 / origin / external storage?}
    D -->|Yes| E[Use Transformations]
    D -->|No| F{Need direct user uploads?}
    F -->|Yes| C
    F -->|No| E

    C --> G[Best for UGC apps, media pipelines, signed delivery, variants]
    E --> H[Best for existing storage, gradual migration, image resizing on demand]

Rule of thumb

Use Hosted Images when:

Use Transformations when:


3. Core concepts

3.1 Hosted Images

Images are uploaded into Cloudflare Images storage and delivered using image delivery URLs.

Typical delivery shape:

https://imagedelivery.net/<ACCOUNT_HASH>/<IMAGE_ID>/<VARIANT>

Or via a custom domain:

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

3.2 Transformations

Cloudflare transforms images that are stored outside of Hosted Images.

Typical URL shape:

https://example.com/cdn-cgi/image/<OPTIONS>/<SOURCE_PATH_OR_URL>

Examples:

https://example.com/cdn-cgi/image/width=800,quality=85,format=auto/images/hero.jpg
https://example.com/cdn-cgi/image/fit=cover,width=400,height=400,format=auto/https://assets.example.net/user/123/avatar.jpg

3.3 Variants

A variant is a named transformation profile for Hosted Images, such as thumbnail, card, hero, or public.

Variants are useful because they:

3.4 Flexible variants

Flexible variants extend Hosted Images by allowing dynamic resizing parameters on Cloudflare-hosted images.

This is more flexible than named variants, but it can also increase complexity and image-shape sprawl if you do not constrain usage.

3.5 Signed URLs

Hosted Images supports signed URL tokens for private delivery. You can require signed URLs at the image level, while optionally keeping some variants public.

3.6 Direct Creator Upload

This lets end users upload directly to Cloudflare Images using a one-time upload URL, without exposing your API token to the browser or mobile app.


4. Architecture overview

4.1 Hosted Images architecture

flowchart LR
    User[Browser / Mobile App] --> App[Your App Backend]
    App --> CFAPI[Cloudflare Images API]
    CFAPI --> UploadURL[One-time Upload URL]
    UploadURL --> User
    User --> CFUpload[upload.imagedelivery.net]
    CFUpload --> ImagesStore[Cloudflare Images Storage]
    ImagesStore --> Delivery[imagedelivery.net or custom domain]
    Delivery --> User

4.2 Transformations architecture

flowchart LR
    User[Browser] --> CF[Cloudflare zone]
    CF --> Transform[/cdn-cgi/image/]
    Transform --> Cache[Cloudflare edge cache]
    Cache -->|miss| Origin[Origin / R2 / remote source]
    Origin --> Cache
    Cache --> User

4.3 Worker-controlled transformation architecture

flowchart LR
    User[Browser] --> Worker[Cloudflare Worker]
    Worker --> Fetch[fetch with cf.image options]
    Fetch --> Source[Origin / R2 / hidden bucket]
    Source --> Worker
    Worker --> User

This model is powerful when you want custom URL schemes, authorization logic, or hidden origin locations.


5. Product capabilities in plain language

5.1 What Hosted Images is good at

Hosted Images is strong when you want an opinionated media pipeline for applications such as:

Main advantages

5.2 What Transformations is good at

Transformations is best when images already live somewhere else.

Typical use cases:

Main advantages


6. Pricing model and how to think about it

Cloudflare Images uses different billing depending on which mode you choose.

6.1 Free plan

The default free plan includes the transformations feature with up to 5,000 unique transformations per month.

If you exceed that:

6.2 Paid plan

The paid plan supports both transformations and Hosted Images storage.

At the time of writing, the published pricing is:

6.3 The most important pricing distinction

For images stored outside of Cloudflare Images, you are billed by unique transformations.

For images stored inside Hosted Images, you are billed by:

That means Hosted Images and Transformations can behave very differently economically.

Example: transformation billing

If you resize the same original image to:

those are three unique transformations for external-source images.

Repeat requests for the exact same transformed version in the same calendar month count only once for transformation billing.

Practical interpretation


7. Limits and supported formats

7.1 Hosted image upload formats

Cloudflare Images accepts these input formats for Hosted Images uploads:

Important note: HEIC can be ingested for decoding, but Cloudflare serves web-safe outputs such as AVIF, WebP, JPG, or PNG.

7.2 Hosted image upload limits

Published upload limits for Hosted Images include:

7.3 External-source transformation limits

For images stored outside of Hosted Images, published limits include:

Practical takeaway

If your source library includes giant editorial TIFF-like assets converted into huge JPEGs, or very large animated GIFs, you need to validate them early in your pipeline.


8. Upload methods for Hosted Images

Cloudflare Images supports several upload patterns.

8.1 Upload from your backend with file bytes

This is the simplest server-side path.

curl --request POST \
  --url https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/images/v1 \
  --header 'Authorization: Bearer <API_TOKEN>' \
  --form file=@./avatar.jpg

Best for

8.2 Upload by URL

Instead of sending bytes, you can provide a source URL and let Cloudflare pull it.

curl --request POST \
  --url https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/images/v1 \
  --header 'Authorization: Bearer <API_TOKEN>' \
  --form 'url=https://example.com/source-images/hero.jpg'

Best for

8.3 Upload with a custom ID path

Instead of Cloudflare-generated UUIDs, you can assign a custom ID such as:

This can make integration easier, especially when your app already has deterministic image identifiers.

Example idea

users/42/avatar
products/ABC-1000/front
brands/nike/logo

Design warning

Do not encode secrets or private business meaning into custom IDs. Treat them as identifiers, not private metadata.

8.4 Direct Creator Upload

This is one of the most useful Hosted Images capabilities.

Flow

  1. Your backend requests a one-time upload URL.
  2. Your backend returns that upload URL to the client.
  3. The browser or mobile app uploads directly to Cloudflare.
  4. Cloudflare stores the image and returns the future image ID.
sequenceDiagram
    participant User as Browser / Mobile App
    participant App as Your Backend
    participant CF as Cloudflare Images API
    participant U as upload.imagedelivery.net

    App->>CF: POST /images/v2/direct_upload
    CF-->>App: image id + one-time uploadURL
    App-->>User: uploadURL
    User->>U: POST image bytes
    U-->>User: upload accepted
    User-->>App: notify completion or continue flow

Example: create one-time upload URL

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={"type":"avatar","userId":"42"}'

Example response shape

{
  "result": {
    "id": "2cdc28f0-017a-49c4-9ed7-87056c83901",
    "uploadURL": "https://upload.imagedelivery.net/<ACCOUNT_HASH>/2cdc28f0-017a-49c4-9ed7-87056c83901"
  },
  "success": true,
  "errors": [],
  "messages": []
}

Why this is a big deal

Without Direct Creator Upload, teams often build this slower path:

flowchart LR
    User[Client] --> App[App Server]
    App --> Temp[Temporary bucket or disk]
    Temp --> CF[Cloudflare Images]

Direct Creator Upload removes the middle storage layer and avoids exposing your Cloudflare API token to the client.


9. Delivery model for Hosted Images

9.1 Default delivery URL

https://imagedelivery.net/<ACCOUNT_HASH>/<IMAGE_ID>/<VARIANT>

Example:

https://imagedelivery.net/ZWd9g1K7eljCn_KDTu_MWA/083eb7b2-5392-4565-b69e-aff66acddd00/public

9.2 Delivery through your custom domain

If you want your own hostname:

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

Why use a custom domain

Example

<img
  src="https://media.example.com/cdn-cgi/imagedelivery/ACCOUNT_HASH/users/42/avatar/public"
  alt="Profile photo"
/>

10. Variants and flexible variants

10.1 Named variants

Hosted Images includes a default public variant. You can define up to 100 variants.

Example variant set:

Variant Intended use Example behavior
public default general display width 1200, format auto
thumb avatars and small cards 120x120 cover
card grid/listing cards 480x320 cover
hero banners 1600x900 fit/crop
original-safe near-original delivery retain size limits + metadata choice

Why named variants are usually better than arbitrary resizing

10.2 Flexible variants

Flexible variants let you apply transformation parameters to Cloudflare-hosted images more dynamically.

Example idea:

https://imagedelivery.net/<ACCOUNT_HASH>/<IMAGE_ID>/w=400,sharpen=3

When flexible variants help

When to be careful

Flexible variants can make it too easy for clients to request many one-off sizes. That can reduce cache reuse and complicate performance governance.

Team recommendation

Even when flexible variants are enabled, constrain usage behind:


11. Signed URLs and private delivery

Hosted Images supports signed URL tokens for private images.

How it works

Important limitation

Private images do not currently support custom paths.

Architecture

sequenceDiagram
    participant Browser
    participant App as Your App Backend
    participant Signer as URL Signer
    participant CF as Cloudflare Images

    Browser->>App: Request private asset URL
    App->>Signer: Generate signed token
    Signer-->>App: Signed URL
    App-->>Browser: Signed URL with expiration
    Browser->>CF: GET signed URL
    CF-->>Browser: Image if token valid

Good use cases

Example pattern

Your backend issues signed URLs that expire in 5 minutes:

https://imagedelivery.net/<ACCOUNT_HASH>/<IMAGE_ID>/public?token=<SIGNED_TOKEN>

Use short expirations unless there is a strong UX reason not to.


12. Transformations for external images

Transformations optimize images stored outside of Cloudflare Images.

12.1 When to use URL-based transformations

URL-based transformations are simple and fast to adopt.

Example:

https://example.com/cdn-cgi/image/width=800,quality=85,format=auto/uploads/blog/cover.jpg

Best for

12.2 When to use Worker-based transformations

Use Workers when you need logic, such as:

Example Worker

export default {
  async fetch(request) {
    const url = new URL(request.url)
    const preset = url.searchParams.get("preset") || "card"

    const presets = {
      thumb: { width: 120, height: 120, fit: "cover", format: "auto" },
      card: { width: 480, height: 320, fit: "cover", format: "auto", quality: 85 },
      hero: { width: 1600, height: 900, fit: "cover", format: "auto", quality: 82 }
    }

    const image = presets[preset] || presets.card

    // Real source is hidden from the client.
    const source = "https://origin.example.internal/assets" + url.pathname

    return fetch(source, {
      cf: {
        image
      }
    })
  }
}

Why this is better than raw width parameters

Instead of letting clients request /image?w=317, /image?w=318, /image?w=319, you normalize them to a small preset set.

That improves:


13. Common transformation options

Cloudflare supports many transformation parameters. The ones teams use most often are:

Example: responsive card image

/cdn-cgi/image/fit=cover,width=480,height=320,quality=85,format=auto/products/shoe.jpg

Example: hero image

/cdn-cgi/image/fit=cover,width=1600,height=900,quality=82,format=auto/campaigns/spring-hero.jpg

Example: preserve GIF frames

/cdn-cgi/image/anim=true,width=500,format=auto/memes/cat.gif

Example: flatten animation to still image

/cdn-cgi/image/anim=false,width=500,format=webp/memes/cat.gif

That is especially useful when user-uploaded animated GIFs are too heavy.

Quality guidance

Cloudflare documents quality on a 1-100 scale, with 85 as the default and useful values often between 50 and 90.

A practical starting point:


14. Responsive images done right

Cloudflare’s docs show standard srcset usage. The main lesson is: do not create too many density variants.

Good baseline

Use:

Usually stop there.

Example

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

Better example with width descriptors

<img
  src="/cdn-cgi/image/width=640,quality=80,format=auto/blog/cover.jpg"
  srcset="
    /cdn-cgi/image/width=320,quality=80,format=auto/blog/cover.jpg 320w,
    /cdn-cgi/image/width=640,quality=80,format=auto/blog/cover.jpg 640w,
    /cdn-cgi/image/width=960,quality=80,format=auto/blog/cover.jpg 960w,
    /cdn-cgi/image/width=1280,quality=80,format=auto/blog/cover.jpg 1280w
  "
  sizes="(max-width: 640px) 100vw, (max-width: 1200px) 50vw, 640px"
  alt="Blog cover"
/>

Governance tip

Snap widths to a fixed set such as:

Avoid arbitrary width generation.


15. Custom domains and custom paths

15.1 Hosted Images custom domains

Hosted Images can be served from customer domains under the same Cloudflare account.

Example:

https://media.example.com/cdn-cgi/imagedelivery/<ACCOUNT_HASH>/<IMAGE_ID>/<VARIANT>

15.2 Transformations custom paths

By default, Transformations use /cdn-cgi/image/, but Cloudflare supports Transform Rules to rewrite friendly paths.

Goal

Turn this:

https://example.com/cdn-cgi/image/width=600,quality=85,format=auto/images/photo.jpg

into something like this:

https://example.com/images/photo.jpg?width=600

or:

https://example.com/img/600x400/photo.jpg

Basic rewrite idea

flowchart LR
    Friendly[/images/.../] --> Rule[Transform Rule]
    Rule --> CGI[/cdn-cgi/image/.../]
    CGI --> Origin[Origin image]

Important warning

When you rewrite custom paths for transformations, you must avoid rewrite loops. Cloudflare specifically documents checking the Via header for image-resizing when building rules.

Practical implication

Custom paths are powerful, but if you are not careful, you can accidentally rewrite a transformed request back into itself.


16. R2 integration patterns

Cloudflare Images and R2 work very well together, but there are two very different patterns.

16.1 Pattern A: Store originals in R2, use Transformations

flowchart LR
    Editor[Uploader / CMS] --> R2[R2 bucket]
    Browser --> CF[Cloudflare zone]
    CF --> Resize[/cdn-cgi/image/]
    Resize --> R2
    Resize --> Edge[Edge cache]
    Edge --> Browser

Best for

Example

https://media.example.com/cdn-cgi/image/width=800,format=auto/https://pub-<bucket>.r2.dev/articles/2026/cover.jpg

Or better, use an R2 custom domain rather than public bucket URLs.

16.2 Pattern B: Use Hosted Images for app uploads, R2 for everything else

This hybrid is often the best real-world design.

Why this is attractive

You avoid forcing one system to do everything.


17. Worker integration patterns

Workers gives you programmatic control over the image pipeline.

17.1 Preset mapping Worker

This is the most practical Worker pattern.

const presets = {
  avatar: { width: 120, height: 120, fit: "cover", format: "auto" },
  card: { width: 480, height: 320, fit: "cover", format: "auto", quality: 85 },
  hero: { width: 1600, height: 900, fit: "cover", format: "auto", quality: 82 }
}

export default {
  async fetch(request) {
    const url = new URL(request.url)
    const preset = presets[url.searchParams.get("preset")] || presets.card
    const source = "https://assets.example.com" + url.pathname

    return fetch(source, {
      cf: { image: preset }
    })
  }
}

17.2 Width allowlist Worker

Useful when frontend teams need flexibility but you still want guardrails.

const ALLOWED = [160, 320, 480, 640, 960, 1280, 1600]

function nearestWidth(input) {
  const n = Number(input || 640)
  return ALLOWED.reduce((best, cur) =>
    Math.abs(cur - n) < Math.abs(best - n) ? cur : best
  )
}

export default {
  async fetch(request) {
    const url = new URL(request.url)
    const width = nearestWidth(url.searchParams.get("w"))
    const source = "https://origin.example.com" + url.pathname

    return fetch(source, {
      cf: {
        image: {
          width,
          format: "auto",
          quality: 82
        }
      }
    })
  }
}

17.3 Auth-gated transformation Worker

If you need app-session checks before image delivery, a Worker is often the cleanest control plane.

export default {
  async fetch(request) {
    const cookie = request.headers.get("cookie") || ""
    if (!cookie.includes("session=")) {
      return new Response("Unauthorized", { status: 401 })
    }

    const url = new URL(request.url)
    const source = "https://private-origin.example.internal" + url.pathname

    return fetch(source, {
      cf: {
        image: {
          width: 800,
          format: "auto",
          quality: 80
        }
      }
    })
  }
}

Security reminder

If you use Worker-based transformations, make sure users cannot force your Worker to resize arbitrary third-party URLs unless that is truly intended.


18. Practical architecture playbooks

18.1 User-generated content app

Good choice

Use Hosted Images + Direct Creator Upload + named variants + signed URLs where needed.

flowchart LR
    User[User app] --> Backend[App backend]
    Backend --> DCU[Direct upload URL]
    DCU --> User
    User --> CFUpload[Cloudflare upload endpoint]
    CFUpload --> Hosted[Hosted Images]
    Hosted --> Variants[thumb / card / public]
    Variants --> Feed[App feed pages]

Why this works

18.2 Ecommerce catalog

Good choice

Use R2 or existing origin + Transformations, unless you want to replatform the whole catalog.

flowchart LR
    CMS[Catalog CMS] --> Origin[Origin or R2]
    Browser --> CF[Cloudflare]
    CF --> Resize[/cdn-cgi/image/]
    Resize --> Origin
    Resize --> Cache[Edge cache]
    Cache --> Browser

Why this works

18.3 Media CMS with editorial uploads

Good choice

Hybrid:

This prevents you from forcing enormous newsroom asset workflows into a purely per-image Hosted Images model if your editorial stack already centers around object storage.

18.4 SaaS dashboards with private images

Good choice

Use Hosted Images + signed URLs for images tied to a specific tenant or customer session.


19. Operational best practices

19.1 Prefer a small set of sizes

Whether you use Hosted Images variants or external Transformations, standardize widths and aspect ratios.

Good

Bad

19.2 Keep transformation URLs deterministic

If your URL changes order or parameter shape unnecessarily, you can hurt cache reuse.

19.3 Use format=auto unless you have a reason not to

Cloudflare automatically chooses efficient output formats like AVIF or WebP when appropriate.

19.4 Validate uploads before they hit the image pipeline

Especially for:

19.5 Treat SVG differently

Cloudflare can deliver SVGs and sanitize them, but does not resize SVGs because SVG is inherently scalable.

19.6 Use a custom domain if image hostnames matter to your app

A custom domain can simplify:

19.7 Separate API logic from image delivery logic

If you are already using Workers for APIs, avoid forcing every image request through heavy app logic unless you truly need that logic. Let static image delivery stay as direct and cacheable as possible.


20. Troubleshooting guide

20.1 Common error codes

Cloudflare documents several relevant errors, including:

20.2 Diagnostic checklist

If images are slow

Check:

  1. Is the request cacheable?
  2. Are you using too many unique widths?
  3. Are images going through unnecessary Worker logic?
  4. Is the source image too large?
  5. Is the origin or R2 path slow on cache misses?
  6. Are you serving originals where transformed variants should be used?

If images fail to resize

Check:

  1. Is the source format supported?
  2. Is the image too large?
  3. Are you trying to resize SVG?
  4. Is the transformed path or URL rewrite looping?
  5. Are you exceeding free-plan transformation limits?

If private delivery fails

Check:

  1. Is the signed token valid and unexpired?
  2. Are you accidentally using custom paths for a private image flow?
  3. Did your backend sign the exact URL form the client is requesting?

21. Practical examples

21.1 Example: avatar system with Hosted Images

Requirements

Data model example

{
  "userId": 42,
  "avatarImageId": "users/42/avatar",
  "avatarUpdatedAt": "2026-04-06T09:00:00Z"
}

Frontend example

<img
  src="https://media.example.com/cdn-cgi/imagedelivery/ACCOUNT_HASH/users/42/avatar/thumb"
  width="60"
  height="60"
  alt="User avatar"
/>

21.2 Example: blog cover images with Transformations

Requirements

Example

<img
  src="/cdn-cgi/image/width=640,quality=80,format=auto/blog/2026/launch-cover.jpg"
  srcset="
    /cdn-cgi/image/width=320,quality=80,format=auto/blog/2026/launch-cover.jpg 320w,
    /cdn-cgi/image/width=640,quality=80,format=auto/blog/2026/launch-cover.jpg 640w,
    /cdn-cgi/image/width=960,quality=80,format=auto/blog/2026/launch-cover.jpg 960w,
    /cdn-cgi/image/width=1280,quality=80,format=auto/blog/2026/launch-cover.jpg 1280w
  "
  sizes="100vw"
  alt="Launch cover"
/>

21.3 Example: private customer report images

Requirements

21.4 Example: migration from another image CDN

Requirements

Phase 1:

Phase 2:

Phase 3:


22. Anti-patterns to avoid

22.1 Letting clients invent infinite widths

This hurts both cache reuse and predictability.

22.2 Running every image through a Worker when simple delivery would do

Only use Worker logic when it adds real value.

22.3 Treating animated GIFs like normal JPEGs

They can explode in size and processing cost.

22.4 Using private image flows without thinking about URL expiration

Over-long expirations weaken access control.

22.5 Forcing Hosted Images when R2 + Transformations is clearly a better fit

If you already have a robust object storage pipeline, Hosted Images is not automatically the right answer.

22.6 Relying on SVG resizing

Cloudflare will deliver SVGs, but resizing is not the model for them.


23.1 Best default for most app teams

flowchart LR
    UGC[User uploaded images] --> Hosted[Hosted Images]
    Static[Editorial / catalog originals] --> R2[R2 or origin]
    Hosted --> Signed[Signed or public delivery]
    R2 --> Transform[Transformations]
    Signed --> App[Application]
    Transform --> App

Why this is strong:

23.2 Best default for greenfield social or community apps

flowchart LR
    Client --> Backend
    Backend --> DirectUpload[Direct Creator Upload]
    Client --> CloudflareUpload[Upload endpoint]
    CloudflareUpload --> Hosted[Hosted Images]
    Hosted --> Feed[Feed and profile variants]
    Hosted --> Private[Signed private variants when needed]

23.3 Best default for existing commerce/catalog sites

flowchart LR
    CMS --> R2orOrigin[R2 or existing origin]
    Browser --> Resize[/cdn-cgi/image/]
    Resize --> R2orOrigin
    Resize --> Cache[Edge cache]
    Cache --> Browser

24. Implementation checklist

If you choose Hosted Images

If you choose Transformations


25. Bottom line

Cloudflare Images is not just an “image CDN.” It is a flexible image platform with two distinct operating modes:

If your app accepts user uploads, needs clean variant management, or benefits from signed image delivery, Hosted Images is often the best fit.

If your images already live in R2 or origin storage and you mainly want optimization, caching, and responsive delivery, Transformations is usually the simpler and more incremental choice.

For many teams, the best architecture is not choosing one or the other exclusively. It is using:

That hybrid model is usually the most practical, scalable, and operationally sane setup.


26. References

This guide was prepared from Cloudflare’s current product and API documentation, including: