Cloudflare Images
Status: Production Ready โ
Last Updated: 2026-01-21
Dependencies: Cloudflare account with Images enabled
Latest Versions: Cloudflare Images API v2, @cloudflare/[email protected]
Recent Updates (2025):
- February 2025: Content Credentials support (C2PA standard) - preserve image provenance chains, automatic cryptographic signing of transformations
- August 2025: AI Face Cropping GA (
gravity=face with zoom control, GPU-based RetinaFace, 99.4% precision)
- May 2025: Media Transformations origin restrictions (default: same-domain only, configurable via dashboard)
- Upcoming: Background removal, generative upscale (planned features)
Deprecation Notice: Mirage deprecated September 15, 2025. Migrate to Cloudflare Images for storage/transformations or use native <img loading="lazy"> for lazy loading.
Overview
Two features: Images API (upload/store with variants) and Image Transformations (resize any image via URL or Workers).
Quick Start
1. Enable: Dashboard โ Images โ Get Account ID + API token (Cloudflare Images: Edit permission)
2. Upload:
curl -X POST https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/images/v1 \
-H 'Authorization: Bearer <API_TOKEN>' \
-H 'Content-Type: multipart/form-data' \
-F 'file=@./image.jpg'
3. Serve: https://imagedelivery.net/<ACCOUNT_HASH>/<IMAGE_ID>/public
4. Transform (optional): Dashboard โ Images โ Transformations โ Enable for zone
<img src="/cdn-cgi/image/width=800,quality=85/uploads/photo.jpg" />
Upload Methods
1. File Upload: POST to /images/v1 with file (multipart/form-data), optional id, requireSignedURLs, metadata
2. Upload via URL: POST with url=https://example.com/image.jpg (supports HTTP basic auth)
3. Direct Creator Upload (one-time URLs, no API key exposure):
Backend: POST to /images/v2/direct_upload โ returns uploadURL
Frontend: POST file to uploadURL with FormData
CRITICAL CORS FIX:
- โ
Use
multipart/form-data (let browser set header)
- โ
Name field
file (NOT image)
- โ
Call
/direct_upload from backend only
- โ Don't set
Content-Type: application/json
- โ Don't call
/direct_upload from browser
Image Transformations
URL: /cdn-cgi/image/<OPTIONS>/<SOURCE>
- Sizing:
width=800,height=600,fit=cover
- Quality:
quality=85 (1-100)
- Format:
format=auto (WebP/AVIF auto-detection)
- Cropping:
gravity=auto (smart crop), gravity=face (AI face detection, Aug 2025 GA), gravity=center, zoom=0.5 (0-1 range, face crop tightness)
- Effects:
blur=10,sharpen=3,brightness=1.2
- Fit:
scale-down, contain, cover, crop, pad
Workers: Use cf.image object in fetch
fetch(imageURL, {
cf: {
image: { width: 800, quality: 85, format: 'auto', gravity: 'face', zoom: 0.8 }
}
});
Variants
Named Variants (up to 100): Predefined transformations (e.g., avatar, thumbnail)
- Create: POST to
/images/v1/variants with id, options
- Use:
imagedelivery.net/<HASH>/<ID>/avatar
- Works with signed URLs
Flexible Variants: Dynamic params in URL (w=400,sharpen=3)
- Enable: PATCH
/images/v1/config with {"flexible_variants": true}
- โ Cannot use with signed URLs (use named variants instead)
Signed URLs
Generate HMAC-SHA256 tokens for private images (URL format: ?exp=<TIMESTAMP>&sig=<HMAC>).
Algorithm: HMAC-SHA256(signingKey, imageId + variant + expiry) โ hex signature
See: templates/signed-urls-generation.ts for Workers implementation
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 16 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:
const formData = new FormData();
formData.append('file', fileInput.files[0]);
await fetch(uploadURL, {
method: 'POST',
body: formData
});
await fetch(uploadURL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
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;
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:
formData.append('file', imageFile);
formData.append('image', imageFile);
formData.append('photo', imageFile);
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:
fetch(imageURL, {
cf: {
image: {
width: 800,
quality: 85,
format: 'auto'
}
}
});
fetch(imageURL, {
cf: {
image: {
width: 'large',
quality: 150
}
}
});
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:
if (url.pathname.startsWith('/images/')) {
const originalPath = url.pathname.replace('/images/', '');
const originURL = `https://storage.example.com/${originalPath}`;