cloudflare-images
π―Skillfrom ovachiever/droid-tings
Uploads, stores, transforms, and serves images via Cloudflare Images API with global CDN, automatic optimization, and flexible variants.
Part of
ovachiever/droid-tings(370 items)
Installation
git clone https://github.com/ovachiever/droid-tings.gitSkill Details
|
Overview
# 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
```bash
curl --request POST \
--url https://api.cloudflare.com/client/v4/accounts/
--header 'Authorization: Bearer
--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
```html
```
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:
```html

```
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
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)
```bash
curl --request POST \
https://api.cloudflare.com/client/v4/accounts/{account_id}/images/v1 \
--header "Authorization: Bearer
--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:
```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
--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):
```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
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!');
}
});
```
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://
```
Example:
```html

```
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:
```html

```
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
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,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:
```bash
curl "https://api.cloudflare.com/client/v4/accounts/{account_id}/images/v1/variants" \
--header "Authorization: Bearer
--header "Content-Type: application/json" \
--data '{
"id": "avatar",
"options": {
"fit": "cover",
"width": 200,
"height": 200,
"metadata": "none"
},
"neverRequireSignedURLs": false
}'
```
Use in URLs:
```html
```
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
--header "Content-Type: application/json" \
--data '{"flexible_variants": true}'
```
Use in URLs:
```html
```
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/
```
Generate Signature (Workers example):
```typescript
async function generateSignedURL(
imageId: string,
variant: string,
expirySeconds: number = 3600
): Promise
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
srcset="
https://imagedelivery.net/
https://imagedelivery.net/
https://imagedelivery.net/
"
sizes="(max-width: 480px) 480px, (max-width: 768px) 768px, 1920px"
src="https://imagedelivery.net/
alt="Responsive image"
/>
```
Using Flexible Variants:
```html
srcset="
https://imagedelivery.net/
https://imagedelivery.net/
https://imagedelivery.net/
"
sizes="(max-width: 480px) 480px, (max-width: 768px) 768px, 1920px"
src="https://imagedelivery.net/
alt="Responsive image"
/>
```
Art Direction (different crops for mobile vs desktop):
```html
media="(max-width: 767px)"
srcset="https://imagedelivery.net/
/>
media="(min-width: 768px)"
srcset="https://imagedelivery.net/
/>
```
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](https://community.cloudflare.com/t/direct-image-upload-cors-error/345739), [#368114](https://community.cloudflare.com/t/cloudflare-images-direct-upload-cors-problem/368114)
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
Source: [Cloudflare Community #571336](https://community.cloudflare.com/t/images-direct-creator-upload-error-5408/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
```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
Source: [Cloudflare Community #487629](https://community.cloudflare.com/t/direct-creator-upload-returning-400/487629)
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
Source: [Cloudflare Community #306805](https://community.cloudflare.com/t/cors-error-when-using-direct-creator-upload/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](https://developers.cloudflare.com/images/reference/troubleshooting/)
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
Source: [Cloudflare Images Docs - Troubleshooting](https://developers.cloudflare.com/images/reference/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](https://developers.cloudflare.com/images/reference/troubleshooting/)
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
Source: [Cloudflare Images Docs - Troubleshooting](https://developers.cloudflare.com/images/reference/troubleshooting/)
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
Source: [Cloudflare Images Docs - Troubleshooting](https://developers.cloudflare.com/images/reference/troubleshooting/)
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
Source: [Cloudflare Images Docs - Troubleshooting](https://developers.cloudflare.com/images/reference/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
```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
Source: [Cloudflare Images Docs - Enable flexible variants](https://developers.cloudflare.com/images/manage-images/enable-flexible-variants/)
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
Source: [Cloudflare Images Docs - SVG files](https://developers.cloudflare.com/images/transform-images/#svg-files)
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/
// 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](https://developers.cloudflare.com/images/transform-images/transform-via-url/#metadata)
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 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:
```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:
- 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/
```
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/${substring(http.request.uri.path, 7)}
Now /images/{id}/{variant} β /cdn-cgi/imagedelivery/{hash}/{id}/{variant}
See: [Serve images from custom domains](https://developers.cloudflare.com/images/manage-images/serve-images/serve-from-custom-domains/)
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
--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):
```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
See: [Configure webhooks](https://developers.cloudflare.com/images/manage-images/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
More from this repository10
nextjs-shadcn-builder skill from ovachiever/droid-tings
security-auditor skill from ovachiever/droid-tings
threejs-graphics-optimizer skill from ovachiever/droid-tings
api-documenter skill from ovachiever/droid-tings
secret-scanner skill from ovachiever/droid-tings
readme-updater skill from ovachiever/droid-tings
applying-brand-guidelines skill from ovachiever/droid-tings
Configures Tailwind v4 with shadcn/ui, automating CSS variable setup, dark mode, and preventing common initialization errors.
deep-reading-analyst skill from ovachiever/droid-tings
dependency-auditor skill from ovachiever/droid-tings