cloudflare-images

Original🇺🇸 English
Translated
9 scriptsChecked / no sensitive code detected

Store and transform images with Cloudflare Images API and transformations. Use when: uploading images, implementing direct creator uploads, creating variants, generating signed URLs, optimizing formats (WebP/AVIF), transforming via Workers, or debugging CORS, multipart, or error codes 9401-9413.

2installs
Added on

NPX Install

npx skill4agent add ovachiever/droid-tings cloudflare-images

Cloudflare Images

Status: Production Ready ✅ Last Updated: 2025-10-26 Dependencies: Cloudflare account with Images enabled Latest Versions: Cloudflare Images API v2

Overview

Cloudflare Images provides two powerful features:
  1. Images API: Upload, store, and serve images with automatic optimization and variants
  2. Image Transformations: Resize, optimize, and transform any publicly accessible image
Key Benefits:
  • Global CDN delivery
  • Automatic WebP/AVIF conversion
  • Variants for different use cases (up to 100)
  • Direct creator upload (user uploads without API keys)
  • Signed URLs for private images
  • Transform any image via URL or Workers

Quick Start (5 Minutes)

1. Enable Cloudflare Images

Log into Cloudflare dashboard → Images → Enable for your account.
Get your Account ID and create an API token with Cloudflare Images: Edit permissions.
Why this matters:
  • Account ID and API token are required for all API operations
  • Images Free plan includes limited transformations

2. Upload Your First Image

bash
curl --request POST \
  --url https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/images/v1 \
  --header 'Authorization: Bearer <API_TOKEN>' \
  --header 'Content-Type: multipart/form-data' \
  --form 'file=@./image.jpg'
Response includes:
  • id
    : Image ID for serving
  • variants
    : Array of delivery URLs
CRITICAL:
  • Use
    multipart/form-data
    encoding (NOT
    application/json
    )
  • Image ID is automatically generated (or use custom ID)

3. Serve the Image

html
<img src="https://imagedelivery.net/<ACCOUNT_HASH>/<IMAGE_ID>/public" />
Default
public
variant serves the image. Replace with your own variant names.

4. Enable Image Transformations

Dashboard → ImagesTransformations → Select your zone → Enable for zone
Now you can transform ANY image:
html
<img src="/cdn-cgi/image/width=800,quality=85/uploads/photo.jpg" />
Why this matters:
  • Works on images stored OUTSIDE Cloudflare Images
  • Automatic caching on Cloudflare's global network
  • No additional storage costs

5. Transform via Workers (Advanced)

typescript
export default {
  async fetch(request: Request): Promise<Response> {
    const imageURL = "https://example.com/image.jpg";

    return fetch(imageURL, {
      cf: {
        image: {
          width: 800,
          quality: 85,
          format: "auto" // WebP/AVIF for supporting browsers
        }
      }
    });
  }
};

The 3-Feature System

Feature 1: Images API (Upload & Storage)

Store images on Cloudflare's network and serve them globally.
Upload Methods:
  1. File Upload - Upload files directly from your server
  2. Upload via URL - Ingest images from external URLs
  3. Direct Creator Upload - Generate one-time upload URLs for user uploads
Serving Options:
  • Default domain:
    imagedelivery.net
  • Custom domains:
    /cdn-cgi/imagedelivery/...
  • Signed URLs: Private images with expiry tokens
See:
templates/upload-api-basic.ts
,
templates/direct-creator-upload-backend.ts

Feature 2: Image Transformations

Optimize and resize ANY image (stored in Images or external).
Two Methods:
  1. URL Transformations - Special URL format
  2. Workers Transformations - Programmatic control via fetch
Common Transformations:
  • Resize:
    width=800,height=600,fit=cover
  • Optimize:
    quality=85,format=auto
  • Effects:
    blur=10,sharpen=3
  • Crop:
    gravity=face,zoom=0.5
See:
templates/transform-via-url.ts
,
templates/transform-via-workers.ts

Feature 3: Variants

Predefined image sizes for different use cases.
Named Variants (up to 100):
  • Create once, use everywhere
  • Example:
    thumbnail
    ,
    avatar
    ,
    hero
  • Consistent transformations
Flexible Variants (dynamic):
  • Enable per account
  • Use transformation params in URL
  • Example:
    w=400,sharpen=3
  • Cannot use with signed URLs
See:
templates/variants-management.ts
,
references/variants-guide.md

Images API - Upload Methods

Method 1: File Upload (Basic)

bash
curl --request POST \
  https://api.cloudflare.com/client/v4/accounts/{account_id}/images/v1 \
  --header "Authorization: Bearer <API_TOKEN>" \
  --header "Content-Type: multipart/form-data" \
  --form 'file=@./image.jpg' \
  --form 'requireSignedURLs=false' \
  --form 'metadata={"key":"value"}'
Key Options:
  • file
    : Image file (required)
  • id
    : Custom ID (optional, default auto-generated)
  • requireSignedURLs
    :
    true
    for private images (default:
    false
    )
  • metadata
    : JSON object (max 1024 bytes, not visible to end users)
Response:
json
{
  "result": {
    "id": "2cdc28f0-017a-49c4-9ed7-87056c83901",
    "filename": "image.jpg",
    "uploaded": "2022-01-31T16:39:28.458Z",
    "requireSignedURLs": false,
    "variants": [
      "https://imagedelivery.net/Vi7wi5KSItxGFsWRG2Us6Q/2cdc28f0.../public"
    ]
  }
}
See:
templates/upload-api-basic.ts

Method 2: Upload via URL

Ingest images from external sources without downloading first.
bash
curl --request POST \
  https://api.cloudflare.com/client/v4/accounts/{account_id}/images/v1 \
  --header "Authorization: Bearer <API_TOKEN>" \
  --form 'url=https://example.com/image.jpg' \
  --form 'metadata={"source":"external"}'
When to use:
  • Migrating images from another service
  • Ingesting user-provided URLs
  • Backing up images from external sources
CRITICAL:
  • URL must be publicly accessible or authenticated
  • Supports HTTP basic auth:
    https://user:password@example.com/image.jpg
  • Cannot use both
    file
    and
    url
    in same request
See:
templates/upload-via-url.ts

Method 3: Direct Creator Upload ⭐

Generate one-time upload URLs for users to upload directly to Cloudflare (no API key exposure).
Backend Endpoint (generate upload URL):
typescript
const response = await fetch(
  `https://api.cloudflare.com/client/v4/accounts/${accountId}/images/v2/direct_upload`,
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      requireSignedURLs: true,
      metadata: { userId: '12345' },
      expiry: '2025-10-26T18:00:00Z' // Optional: default 30min, max 6hr
    })
  }
);

const { uploadURL, id } = await response.json();
// Return uploadURL to frontend
Frontend Upload (HTML + JavaScript):
html
<form id="upload-form">
  <input type="file" id="file-input" accept="image/*" />
  <button type="submit">Upload</button>
</form>

<script>
document.getElementById('upload-form').addEventListener('submit', async (e) => {
  e.preventDefault();

  const fileInput = document.getElementById('file-input');
  const formData = new FormData();
  formData.append('file', fileInput.files[0]); // MUST be named 'file'

  const uploadURL = 'UPLOAD_URL_FROM_BACKEND'; // Get from backend

  const response = await fetch(uploadURL, {
    method: 'POST',
    body: formData // NO Content-Type header, browser sets multipart/form-data
  });

  if (response.ok) {
    console.log('Upload successful!');
  }
});
</script>
Why this matters:
  • No API key exposure to browser
  • Users upload directly to Cloudflare (faster, no intermediary server)
  • One-time URL expires after use or timeout
  • Webhooks available for upload success/failure notifications
CRITICAL CORS FIX:
  • DO: Use
    multipart/form-data
    encoding (let browser set header)
  • DO: Name field
    file
    (NOT
    image
    or other names)
  • DO: Call
    /direct_upload
    API from backend only
  • DON'T: Set
    Content-Type: application/json
    or
    image/jpeg
  • DON'T: Call
    /direct_upload
    from browser (CORS will fail)
See:
templates/direct-creator-upload-backend.ts
,
templates/direct-creator-upload-frontend.html
,
references/direct-upload-complete-workflow.md

Image Transformations

URL Transformations

Transform images using a special URL format.
URL Pattern:
https://<ZONE>/cdn-cgi/image/<OPTIONS>/<SOURCE-IMAGE>
Example:
html
<img src="/cdn-cgi/image/width=800,quality=85,format=auto/uploads/photo.jpg" />
Common Options:
  • Sizing:
    width=800
    ,
    height=600
    ,
    fit=cover
  • Quality:
    quality=85
    (1-100)
  • Format:
    format=auto
    (WebP/AVIF auto-detection),
    format=webp
    ,
    format=jpeg
  • Cropping:
    gravity=auto
    (smart crop),
    gravity=face
    ,
    trim=10
  • Effects:
    blur=10
    ,
    sharpen=3
    ,
    brightness=1.2
    ,
    contrast=1.1
  • Rotation:
    rotate=90
    ,
    flip=h
    (horizontal),
    flip=v
    (vertical)
Fit Options:
  • scale-down
    : Shrink to fit (never enlarge)
  • contain
    : Resize to fit within dimensions (preserve aspect ratio)
  • cover
    : Resize to fill dimensions (may crop)
  • crop
    : Crop to exact dimensions
  • pad
    : Resize and add padding (use with
    background
    option)
Format Auto-Detection:
html
<img src="/cdn-cgi/image/format=auto/image.jpg" />
Cloudflare serves:
  • AVIF to browsers that support it (Chrome, Edge)
  • WebP to browsers without AVIF support (Safari, Firefox)
  • Original format (JPEG) as fallback
See:
templates/transform-via-url.ts
,
references/transformation-options.md

Workers Transformations

Programmatic image transformations with custom URL schemes.
Basic Pattern:
typescript
export default {
  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);

    // Custom URL scheme: /images/thumbnail/photo.jpg
    if (url.pathname.startsWith('/images/thumbnail/')) {
      const imagePath = url.pathname.replace('/images/thumbnail/', '');
      const imageURL = `https://storage.example.com/${imagePath}`;

      return fetch(imageURL, {
        cf: {
          image: {
            width: 300,
            height: 300,
            fit: 'cover',
            quality: 85
          }
        }
      });
    }

    return new Response('Not found', { status: 404 });
  }
};
Advanced: Content Negotiation:
typescript
const accept = request.headers.get('accept') || '';

let format: 'avif' | 'webp' | 'auto' = 'auto';
if (/image\/avif/.test(accept)) {
  format = 'avif';
} else if (/image\/webp/.test(accept)) {
  format = 'webp';
}

return fetch(imageURL, {
  cf: {
    image: {
      format,
      width: 800,
      quality: 85
    }
  }
});
Why Workers Transformations:
  • Custom URL schemes: Hide image storage location
  • Preset names: Use
    thumbnail
    ,
    avatar
    ,
    large
    instead of pixel values
  • Content negotiation: Serve optimal format based on browser
  • Access control: Check authentication before serving
  • Dynamic sizing: Calculate dimensions based on device type
See:
templates/transform-via-workers.ts
,
references/transformation-options.md

Variants Management

Named Variants (Up to 100)

Create predefined transformations for different use cases.
Create via API:
bash
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": "avatar",
    "options": {
      "fit": "cover",
      "width": 200,
      "height": 200,
      "metadata": "none"
    },
    "neverRequireSignedURLs": false
  }'
Use in URLs:
html
<img src="https://imagedelivery.net/<ACCOUNT_HASH>/<IMAGE_ID>/avatar" />
When to use:
  • Consistent image sizes across your app
  • Private images (works with signed URLs)
  • Simple, predictable URLs
See:
templates/variants-management.ts

Flexible Variants

Dynamic transformations using params in URL.
Enable (per account, one-time):
bash
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}'
Use in URLs:
html
<img src="https://imagedelivery.net/<ACCOUNT_HASH>/<IMAGE_ID>/w=400,sharpen=3" />
When to use:
  • Dynamic sizing needs
  • Public images only (cannot use with signed URLs)
  • Rapid prototyping
CRITICAL:
  • Cannot use with
    requireSignedURLs=true
  • Use named variants for private images
See:
references/variants-guide.md

Signed URLs (Private Images)

Generate time-limited URLs for private images using HMAC-SHA256 tokens.
URL Format:
https://imagedelivery.net/<ACCOUNT_HASH>/<IMAGE_ID>/<VARIANT>?exp=<EXPIRY>&sig=<SIGNATURE>
Generate Signature (Workers example):
typescript
async function generateSignedURL(
  imageId: string,
  variant: string,
  expirySeconds: number = 3600
): Promise<string> {
  const accountHash = 'YOUR_ACCOUNT_HASH';
  const signingKey = 'YOUR_SIGNING_KEY'; // Dashboard → Images → Keys

  const expiry = Math.floor(Date.now() / 1000) + expirySeconds;
  const stringToSign = `${imageId}${variant}${expiry}`;

  const encoder = new TextEncoder();
  const keyData = encoder.encode(signingKey);
  const messageData = encoder.encode(stringToSign);

  const cryptoKey = await crypto.subtle.importKey(
    'raw',
    keyData,
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['sign']
  );

  const signature = await crypto.subtle.sign('HMAC', cryptoKey, messageData);
  const sig = Array.from(new Uint8Array(signature))
    .map(b => b.toString(16).padStart(2, '0'))
    .join('');

  return `https://imagedelivery.net/${accountHash}/${imageId}/${variant}?exp=${expiry}&sig=${sig}`;
}
Usage:
typescript
const signedURL = await generateSignedURL('image-id', 'public', 3600);
// Returns URL valid for 1 hour
When to use:
  • User profile photos (private until shared)
  • Paid content (time-limited access)
  • Temporary downloads
  • Secure image delivery
See:
templates/signed-urls-generation.ts
,
references/signed-urls-guide.md

Responsive Images

Serve optimal image sizes for different screen sizes.
Using Named Variants:
html
<img
  srcset="
    https://imagedelivery.net/<HASH>/<ID>/mobile 480w,
    https://imagedelivery.net/<HASH>/<ID>/tablet 768w,
    https://imagedelivery.net/<HASH>/<ID>/desktop 1920w
  "
  sizes="(max-width: 480px) 480px, (max-width: 768px) 768px, 1920px"
  src="https://imagedelivery.net/<HASH>/<ID>/desktop"
  alt="Responsive image"
/>
Using Flexible Variants:
html
<img
  srcset="
    https://imagedelivery.net/<HASH>/<ID>/w=480,f=auto 480w,
    https://imagedelivery.net/<HASH>/<ID>/w=768,f=auto 768w,
    https://imagedelivery.net/<HASH>/<ID>/w=1920,f=auto 1920w
  "
  sizes="(max-width: 480px) 480px, (max-width: 768px) 768px, 1920px"
  src="https://imagedelivery.net/<HASH>/<ID>/w=1920,f=auto"
  alt="Responsive image"
/>
Art Direction (different crops for mobile vs desktop):
html
<picture>
  <source
    media="(max-width: 767px)"
    srcset="https://imagedelivery.net/<HASH>/<ID>/mobile-square"
  />
  <source
    media="(min-width: 768px)"
    srcset="https://imagedelivery.net/<HASH>/<ID>/desktop-wide"
  />
  <img src="https://imagedelivery.net/<HASH>/<ID>/desktop-wide" alt="Hero image" />
</picture>
See:
templates/responsive-images-srcset.html
,
references/responsive-images-patterns.md

Critical Rules

Always Do

✅ Use
multipart/form-data
for Direct Creator Upload ✅ Name the file field
file
(not
image
or other names) ✅ Call
/direct_upload
API from backend only (NOT browser) ✅ Use HTTPS URLs for transformations (HTTP not supported) ✅ URL-encode special characters in image paths ✅ Enable transformations on zone before using
/cdn-cgi/image/
✅ Use named variants for private images (signed URLs) ✅ Check
Cf-Resized
header for transformation errors ✅ Set
format=auto
for automatic WebP/AVIF conversion ✅ Use
fit=scale-down
to prevent unwanted enlargement

Never Do

❌ Use
application/json
Content-Type for file uploads ❌ Call
/direct_upload
from browser (CORS will fail) ❌ Use flexible variants with
requireSignedURLs=true
❌ Resize SVG files (they're inherently scalable) ❌ Use HTTP URLs for transformations (HTTPS only) ❌ Put spaces or unescaped Unicode in URLs ❌ Transform the same image multiple times in Workers (causes 9403 loop) ❌ Exceed 100 megapixels image size ❌ Use
/cdn-cgi/image/
endpoint in Workers (use
cf.image
instead) ❌ Forget to enable transformations on zone before use

Known Issues Prevention

This skill prevents 13+ documented issues.

Issue #1: Direct Creator Upload CORS Error

Error:
Access to XMLHttpRequest blocked by CORS policy: Request header field content-type is not allowed
Why It Happens: Server CORS settings only allow
multipart/form-data
for Content-Type header
Prevention:
javascript
// ✅ CORRECT
const formData = new FormData();
formData.append('file', fileInput.files[0]);
await fetch(uploadURL, {
  method: 'POST',
  body: formData // Browser sets multipart/form-data automatically
});

// ❌ WRONG
await fetch(uploadURL, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' }, // CORS error
  body: JSON.stringify({ file: base64Image })
});

Issue #2: Error 5408 - Upload Timeout

Error:
Error 5408
after ~15 seconds of upload
Why It Happens: Cloudflare has 30-second request timeout; slow uploads or large files exceed limit
Prevention:
  • Compress images before upload (client-side with Canvas API)
  • Use reasonable file size limits (e.g., max 10MB)
  • Show upload progress to user
  • Handle timeout errors gracefully
javascript
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB

if (file.size > MAX_FILE_SIZE) {
  alert('File too large. Please select an image under 10MB.');
  return;
}

Issue #3: Error 400 - Invalid File Parameter

Error:
400 Bad Request
with unhelpful error message
Why It Happens: File field must be named
file
(not
image
,
photo
, etc.)
Prevention:
javascript
// ✅ CORRECT
formData.append('file', imageFile);

// ❌ WRONG
formData.append('image', imageFile); // 400 error
formData.append('photo', imageFile); // 400 error

Issue #4: CORS Preflight Failures

Error: Preflight OPTIONS request blocked
Why It Happens: Calling
/direct_upload
API directly from browser (should be backend-only)
Prevention:
ARCHITECTURE:
Browser → Backend API → POST /direct_upload → Returns uploadURL → Browser uploads to uploadURL
Never expose API token to browser. Generate upload URL on backend, return to frontend.

Issue #5: Error 9401 - Invalid Arguments

Error:
Cf-Resized: err=9401
- Required cf.image options missing or invalid
Why It Happens: Missing required transformation parameters or invalid values
Prevention:
typescript
// ✅ CORRECT
fetch(imageURL, {
  cf: {
    image: {
      width: 800,
      quality: 85,
      format: 'auto'
    }
  }
});

// ❌ WRONG
fetch(imageURL, {
  cf: {
    image: {
      width: 'large', // Must be number
      quality: 150 // Max 100
    }
  }
});

Issue #6: Error 9402 - Image Too Large

Error:
Cf-Resized: err=9402
- Image too large or connection interrupted
Why It Happens: Image exceeds maximum area (100 megapixels) or download fails
Prevention:
  • Validate image dimensions before transforming
  • Use reasonable source images (max 10000x10000px)
  • Handle network errors gracefully

Issue #7: Error 9403 - Request Loop

Error:
Cf-Resized: err=9403
- Worker fetching its own URL or already-resized image
Why It Happens: Transformation applied to already-transformed image, or Worker fetches itself
Prevention:
typescript
// ✅ CORRECT
if (url.pathname.startsWith('/images/')) {
  const originalPath = url.pathname.replace('/images/', '');
  const originURL = `https://storage.example.com/${originalPath}`;
  return fetch(originURL, { cf: { image: { width: 800 } } });
}

// ❌ WRONG
if (url.pathname.startsWith('/images/')) {
  // Fetches worker's own URL, causes loop
  return fetch(request, { cf: { image: { width: 800 } } });
}

Issue #8: Error 9406/9419 - Invalid URL Format

Error:
Cf-Resized: err=9406
or
err=9419
- Non-HTTPS URL or URL has spaces/unescaped Unicode
Why It Happens: Image URL uses HTTP (not HTTPS) or contains invalid characters
Prevention:
typescript
// ✅ CORRECT
const imageURL = "https://example.com/images/photo%20name.jpg";

// ❌ WRONG
const imageURL = "http://example.com/images/photo.jpg"; // HTTP not allowed
const imageURL = "https://example.com/images/photo name.jpg"; // Space not encoded
Always use
encodeURIComponent()
for URL paths:
typescript
const filename = "photo name.jpg";
const imageURL = `https://example.com/images/${encodeURIComponent(filename)}`;

Issue #9: Error 9412 - Non-Image Response

Error:
Cf-Resized: err=9412
- Origin returned HTML instead of image
Why It Happens: Origin server returns 404 page or error page (HTML) instead of image
Prevention:
typescript
// Verify URL before transforming
const originResponse = await fetch(imageURL, { method: 'HEAD' });
const contentType = originResponse.headers.get('content-type');

if (!contentType?.startsWith('image/')) {
  return new Response('Not an image', { status: 400 });
}

return fetch(imageURL, { cf: { image: { width: 800 } } });

Issue #10: Error 9413 - Max Image Area Exceeded

Error:
Cf-Resized: err=9413
- Image exceeds 100 megapixels
Why It Happens: Source image dimensions exceed 100 megapixels (e.g., 10000x10000px)
Prevention:
  • Validate image dimensions before upload
  • Pre-process oversized images
  • Reject images above threshold
typescript
const MAX_MEGAPIXELS = 100;

if (width * height > MAX_MEGAPIXELS * 1_000_000) {
  return new Response('Image too large', { status: 413 });
}

Issue #11: Flexible Variants + Signed URLs Incompatibility

Error: Flexible variants don't work with private images
Why It Happens: Flexible variants cannot be used with
requireSignedURLs=true
Prevention:
typescript
// ✅ CORRECT - Use named variants for private images
await uploadImage({
  file: imageFile,
  requireSignedURLs: true // Use named variants: /public, /avatar, etc.
});

// ❌ WRONG - Flexible variants don't support signed URLs
// Cannot use: /w=400,sharpen=3 with requireSignedURLs=true

Issue #12: SVG Resizing Limitation

Error: SVG files don't resize via transformations
Why It Happens: SVG is inherently scalable (vector format), resizing not applicable
Prevention:
typescript
// SVGs can be served but not resized
// Use any variant name as placeholder
// https://imagedelivery.net/<HASH>/<SVG_ID>/public

// SVG will be served at original size regardless of variant settings

Issue #13: EXIF Metadata Stripped by Default

Error: GPS data, camera settings removed from uploaded JPEGs
Why It Happens: Default behavior strips all metadata except copyright
Prevention:
typescript
// Preserve metadata
fetch(imageURL, {
  cf: {
    image: {
      width: 800,
      metadata: 'keep' // Options: 'none', 'copyright', 'keep'
    }
  }
});
Options:
  • none
    : Strip all metadata
  • copyright
    : Keep only copyright tag (default for JPEG)
  • keep
    : Preserve most EXIF metadata including GPS

Using Bundled Resources

Templates (templates/)

Copy-paste ready code for common patterns:
  1. wrangler-images-binding.jsonc - Wrangler configuration (no binding needed)
  2. upload-api-basic.ts - Upload file to Images API
  3. upload-via-url.ts - Ingest image from external URL
  4. direct-creator-upload-backend.ts - Generate one-time upload URLs
  5. direct-creator-upload-frontend.html - User upload form
  6. transform-via-url.ts - URL transformation examples
  7. transform-via-workers.ts - Workers transformation patterns
  8. variants-management.ts - Create/list/delete variants
  9. signed-urls-generation.ts - HMAC-SHA256 signed URL generation
  10. responsive-images-srcset.html - Responsive image patterns
  11. batch-upload.ts - Batch API for high-volume uploads
Usage:
bash
cp templates/upload-api-basic.ts src/upload.ts
# Edit with your account ID and API token

References (references/)

In-depth documentation Claude can load as needed:
  1. api-reference.md - Complete API endpoints (upload, list, delete, variants)
  2. transformation-options.md - All transform params with examples
  3. variants-guide.md - Named vs flexible variants, when to use each
  4. signed-urls-guide.md - HMAC-SHA256 implementation details
  5. direct-upload-complete-workflow.md - Full architecture and flow
  6. responsive-images-patterns.md - srcset, sizes, art direction
  7. format-optimization.md - WebP/AVIF auto-conversion strategies
  8. top-errors.md - All 13+ errors with detailed troubleshooting
When to load:
  • Deep-dive into specific feature
  • Troubleshooting complex issues
  • Understanding API details
  • Implementing advanced patterns

Scripts (scripts/)

check-versions.sh - Verify API endpoints are current

Advanced Topics

Custom Domains

Serve images from your own domain instead of
imagedelivery.net
.
URL Format:
https://example.com/cdn-cgi/imagedelivery/<ACCOUNT_HASH>/<IMAGE_ID>/<VARIANT>
Requirements:
  • Domain must be on Cloudflare (same account as Images)
  • Proxied through Cloudflare (orange cloud)
Custom Paths (Transform Rules):
Rewrite
/images/...
to
/cdn-cgi/imagedelivery/...
:
  1. Dashboard → Rules → Transform Rules → Rewrite URL
  2. Match:
    starts_with(http.request.uri.path, "/images/")
  3. Rewrite:
    /cdn-cgi/imagedelivery/<ACCOUNT_HASH>${substring(http.request.uri.path, 7)}
Now
/images/{id}/{variant}
/cdn-cgi/imagedelivery/{hash}/{id}/{variant}

Batch API

High-volume uploads with batch tokens.
Host:
batch.imagedelivery.net
(instead of
api.cloudflare.com
)
Usage:
bash
# Create batch token in dashboard: Images → Batch API

curl "https://batch.imagedelivery.net/images/v1" \
  --header "Authorization: Bearer <BATCH_TOKEN>" \
  --form 'file=@./image.jpg'
When to use:
  • Migrating thousands of images
  • Bulk upload workflows
  • Automated image ingestion
See:
templates/batch-upload.ts

Webhooks

Receive notifications for upload success/failure (Direct Creator Upload only).
Setup:
  1. Dashboard → Notifications → Destinations → Webhooks → Create
  2. Enter webhook URL and test
  3. Notifications → All Notifications → Add → Images → Select webhook
Payload (example):
json
{
  "imageId": "2cdc28f0-017a-49c4-9ed7-87056c83901",
  "status": "uploaded",
  "metadata": {"userId": "12345"}
}
When to use:
  • Update database after upload
  • Trigger image processing pipeline
  • Notify user of upload status

Troubleshooting

Problem: Images not transforming

Symptoms:
/cdn-cgi/image/...
returns original image or 404
Solutions:
  1. Enable transformations on zone: Dashboard → Images → Transformations → Enable for zone
  2. Verify zone is proxied through Cloudflare (orange cloud)
  3. Check source image is publicly accessible
  4. Wait 5-10 minutes for settings to propagate

Problem: Direct upload returns CORS error

Symptoms:
Access-Control-Allow-Origin
error in browser console
Solutions:
  1. Use
    multipart/form-data
    encoding (let browser set Content-Type)
  2. Don't call
    /direct_upload
    from browser; call from backend
  3. Name file field
    file
    (not
    image
    )
  4. Remove manual Content-Type header

Problem: Worker transformations return 9403 loop error

Symptoms:
Cf-Resized: err=9403
in response headers
Solutions:
  1. Don't fetch Worker's own URL (use external origin)
  2. Don't transform already-resized images
  3. Check URL routing logic to avoid loops

Problem: Signed URLs not working

Symptoms: 403 Forbidden when accessing signed URL
Solutions:
  1. Verify image uploaded with
    requireSignedURLs=true
  2. Check signature generation (HMAC-SHA256)
  3. Ensure expiry timestamp is in future
  4. Verify signing key matches dashboard (Images → Keys)
  5. Cannot use flexible variants with signed URLs (use named variants)

Problem: Images uploaded but not appearing

Symptoms: Upload returns 200 OK but image not in dashboard
Solutions:
  1. Check for
    draft: true
    in response (Direct Creator Upload)
  2. Wait for upload to complete (check via GET
    /images/v1/{id}
    )
  3. Verify account ID matches
  4. Check for upload errors in webhooks

Complete Setup Checklist

  • Cloudflare account with Images enabled
  • Account ID and API token obtained (Images: Edit permission)
  • (Optional) Image transformations enabled on zone
  • (Optional) Variants created for common use cases
  • (Optional) Flexible variants enabled if dynamic sizing needed
  • (Optional) Signing key obtained for private images
  • (Optional) Webhooks configured for upload notifications
  • (Optional) Custom domain configured with Transform Rules
  • Upload method implemented (file, URL, or direct creator)
  • Serving URLs tested (imagedelivery.net or custom domain)
  • Transformations tested (URL or Workers)
  • Error handling implemented (CORS, timeouts, size limits)

Official Documentation


Package Versions (Verified 2025-10-26)

API Version: v2 (for direct uploads), v1 (for standard uploads)
No npm packages required - uses native Cloudflare APIs
Optional:
  • @cloudflare/workers-types@latest
    - TypeScript types for Workers

Questions? Issues?
  1. Check
    references/top-errors.md
    for common issues
  2. Verify all steps in the setup process
  3. Check official docs: https://developers.cloudflare.com/images/
  4. Ensure transformations are enabled on zone
  5. Verify CORS setup for Direct Creator Upload