| name | cloudflare-images |
| description | This skill provides comprehensive knowledge for Cloudflare Images, covering both the Images API (upload/storage) and Image Transformations (optimize any image). It should be used when uploading images to Cloudflare, resizing images, optimizing image delivery, implementing direct creator uploads, creating image variants, generating signed URLs for private images, transforming images via Workers, or encountering image upload/transformation errors. Use when: setting up Cloudflare Images storage, implementing user-uploaded images, creating responsive images, optimizing image formats (WebP/AVIF), resizing images via URL or Workers, debugging CORS errors with direct uploads, handling image transformation errors (9401-9413), implementing signed URLs, managing image variants, or building image CDNs. Keywords: cloudflare images, image upload cloudflare, imagedelivery.net, cloudflare image transformations, /cdn-cgi/image/, direct creator upload, image variants, cf.image workers, signed urls images, flexible variants, webp avif conversion, responsive images cloudflare, error 5408, error 9401, error 9403, CORS direct upload, multipart/form-data, image optimization cloudflare |
| license | MIT |
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:
- Images API: Upload, store, and serve images with automatic optimization and variants
- 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
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 servingvariants: Array of delivery URLs
CRITICAL:
- Use
multipart/form-dataencoding (NOTapplication/json) - Image ID is automatically generated (or use custom ID)
3. Serve the Image
<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 → Images → Transformations → Select your zone → Enable for zone
Now you can transform ANY image:
<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)
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:
- File Upload - Upload files directly from your server
- Upload via URL - Ingest images from external URLs
- 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:
- URL Transformations - Special URL format
- 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)
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:truefor private images (default:false)metadata: JSON object (max 1024 bytes, not visible to end users)
Response:
{
"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.
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
fileandurlin 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):
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):
<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-dataencoding (let browser set header) - ✅ DO: Name field
file(NOTimageor other names) - ✅ DO: Call
/direct_uploadAPI from backend only - ❌ DON'T: Set
Content-Type: application/jsonorimage/jpeg - ❌ DON'T: Call
/direct_uploadfrom 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:
<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 dimensionspad: Resize and add padding (use withbackgroundoption)
Format Auto-Detection:
<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:
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:
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,largeinstead 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:
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:
<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):
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:
<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):
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:
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:
<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:
<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):
<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
Source: Cloudflare Community #345739, #368114
Why It Happens: Server CORS settings only allow multipart/form-data for Content-Type header
Prevention:
// ✅ 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
Source: Cloudflare Community #571336
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
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
Source: Cloudflare Community #487629
Why It Happens: File field must be named file (not image, photo, etc.)
Prevention:
// ✅ 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
Source: Cloudflare Community #306805
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
Source: Cloudflare Images Docs - Troubleshooting
Why It Happens: Missing required transformation parameters or invalid values
Prevention:
// ✅ 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
Source: Cloudflare Images Docs - Troubleshooting
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
Source: Cloudflare Images Docs - Troubleshooting
Why It Happens: Transformation applied to already-transformed image, or Worker fetches itself
Prevention:
// ✅ 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
Source: Cloudflare Images Docs - Troubleshooting
Why It Happens: Image URL uses HTTP (not HTTPS) or contains invalid characters
Prevention:
// ✅ 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:
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
Source: Cloudflare Images Docs - Troubleshooting
Why It Happens: Origin server returns 404 page or error page (HTML) instead of image
Prevention:
// 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
Source: Cloudflare Images Docs - Troubleshooting
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
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
Source: Cloudflare Images Docs - Enable flexible variants
Why It Happens: Flexible variants cannot be used with requireSignedURLs=true
Prevention:
// ✅ 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
Source: Cloudflare Images Docs - SVG files
Why It Happens: SVG is inherently scalable (vector format), resizing not applicable
Prevention:
// 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
Source: Cloudflare Images Docs - Transform via URL
Why It Happens: Default behavior strips all metadata except copyright
Prevention:
// Preserve metadata
fetch(imageURL, {
cf: {
image: {
width: 800,
metadata: 'keep' // Options: 'none', 'copyright', 'keep'
}
}
});
Options:
none: Strip all metadatacopyright: 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:
- wrangler-images-binding.jsonc - Wrangler configuration (no binding needed)
- upload-api-basic.ts - Upload file to Images API
- upload-via-url.ts - Ingest image from external URL
- direct-creator-upload-backend.ts - Generate one-time upload URLs
- direct-creator-upload-frontend.html - User upload form
- transform-via-url.ts - URL transformation examples
- transform-via-workers.ts - Workers transformation patterns
- variants-management.ts - Create/list/delete variants
- signed-urls-generation.ts - HMAC-SHA256 signed URL generation
- responsive-images-srcset.html - Responsive image patterns
- batch-upload.ts - Batch API for high-volume uploads
Usage:
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:
- api-reference.md - Complete API endpoints (upload, list, delete, variants)
- transformation-options.md - All transform params with examples
- variants-guide.md - Named vs flexible variants, when to use each
- signed-urls-guide.md - HMAC-SHA256 implementation details
- direct-upload-complete-workflow.md - Full architecture and flow
- responsive-images-patterns.md - srcset, sizes, art direction
- format-optimization.md - WebP/AVIF auto-conversion strategies
- 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/...:
- Dashboard → Rules → Transform Rules → Rewrite URL
- Match:
starts_with(http.request.uri.path, "/images/") - Rewrite:
/cdn-cgi/imagedelivery/<ACCOUNT_HASH>${substring(http.request.uri.path, 7)}
Now /images/{id}/{variant} → /cdn-cgi/imagedelivery/{hash}/{id}/{variant}
See: Serve images from custom domains
Batch API
High-volume uploads with batch tokens.
Host: batch.imagedelivery.net (instead of api.cloudflare.com)
Usage:
# 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:
- Dashboard → Notifications → Destinations → Webhooks → Create
- Enter webhook URL and test
- Notifications → All Notifications → Add → Images → Select webhook
Payload (example):
{
"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
See: Configure webhooks
Troubleshooting
Problem: Images not transforming
Symptoms: /cdn-cgi/image/... returns original image or 404
Solutions:
- Enable transformations on zone: Dashboard → Images → Transformations → Enable for zone
- Verify zone is proxied through Cloudflare (orange cloud)
- Check source image is publicly accessible
- Wait 5-10 minutes for settings to propagate
Problem: Direct upload returns CORS error
Symptoms: Access-Control-Allow-Origin error in browser console
Solutions:
- Use
multipart/form-dataencoding (let browser set Content-Type) - Don't call
/direct_uploadfrom browser; call from backend - Name file field
file(notimage) - Remove manual Content-Type header
Problem: Worker transformations return 9403 loop error
Symptoms: Cf-Resized: err=9403 in response headers
Solutions:
- Don't fetch Worker's own URL (use external origin)
- Don't transform already-resized images
- Check URL routing logic to avoid loops
Problem: Signed URLs not working
Symptoms: 403 Forbidden when accessing signed URL
Solutions:
- Verify image uploaded with
requireSignedURLs=true - Check signature generation (HMAC-SHA256)
- Ensure expiry timestamp is in future
- Verify signing key matches dashboard (Images → Keys)
- 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:
- Check for
draft: truein response (Direct Creator Upload) - Wait for upload to complete (check via GET
/images/v1/{id}) - Verify account ID matches
- 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
- Cloudflare Images: https://developers.cloudflare.com/images/
- Get Started: https://developers.cloudflare.com/images/get-started/
- Upload Images: https://developers.cloudflare.com/images/upload-images/
- Direct Creator Upload: https://developers.cloudflare.com/images/upload-images/direct-creator-upload/
- Transform Images: https://developers.cloudflare.com/images/transform-images/
- Transform via URL: https://developers.cloudflare.com/images/transform-images/transform-via-url/
- Transform via Workers: https://developers.cloudflare.com/images/transform-images/transform-via-workers/
- Create Variants: https://developers.cloudflare.com/images/manage-images/create-variants/
- Serve Private Images: https://developers.cloudflare.com/images/manage-images/serve-images/serve-private-images/
- Troubleshooting: https://developers.cloudflare.com/images/reference/troubleshooting/
- API Reference: https://developers.cloudflare.com/api/resources/images/
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?
- Check
references/top-errors.mdfor common issues - Verify all steps in the setup process
- Check official docs: https://developers.cloudflare.com/images/
- Ensure transformations are enabled on zone
- Verify CORS setup for Direct Creator Upload