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.
/plugin marketplace add jezweb/claude-skills/plugin install jezweb-tooling-skills@jezweb/claude-skillsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
README.mdreferences/api-reference.mdreferences/direct-upload-complete-workflow.mdreferences/format-optimization.mdreferences/responsive-images-patterns.mdreferences/signed-urls-guide.mdreferences/top-errors.mdreferences/transformation-options.mdreferences/variants-guide.mdrules/cloudflare-images.mdscripts/check-versions.shtemplates/batch-upload.tstemplates/direct-creator-upload-backend.tstemplates/direct-creator-upload-frontend.htmltemplates/package.jsontemplates/responsive-images-srcset.htmltemplates/signed-urls-generation.tstemplates/transform-via-url.tstemplates/transform-via-workers.tstemplates/upload-api-basic.tsStatus: Production Ready ✅ Last Updated: 2026-01-09 Dependencies: Cloudflare account with Images enabled Latest Versions: Cloudflare Images API v2, @cloudflare/workers-types@4.20260108.0
Recent Updates (2025):
gravity=face with zoom control, GPU-based RetinaFace, 99.4% precision)Two features: Images API (upload/store with variants) and Image Transformations (resize any image via URL or Workers).
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" />
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:
multipart/form-data (let browser set header)file (NOT image)/direct_upload from backend onlyContent-Type: application/json/direct_upload from browserURL: /cdn-cgi/image/<OPTIONS>/<SOURCE>
width=800,height=600,fit=coverquality=85 (1-100)format=auto (WebP/AVIF auto-detection)gravity=auto (smart crop), gravity=face (AI face detection, Aug 2025 GA), gravity=center, zoom=0.5 (0-1 range, face crop tightness)blur=10,sharpen=3,brightness=1.2scale-down, contain, cover, crop, padWorkers: Use cf.image object in fetch
fetch(imageURL, {
cf: {
image: { width: 800, quality: 85, format: 'auto', gravity: 'face', zoom: 0.8 }
}
});
Named Variants (up to 100): Predefined transformations (e.g., avatar, thumbnail)
/images/v1/variants with id, optionsimagedelivery.net/<HASH>/<ID>/avatarFlexible Variants: Dynamic params in URL (w=400,sharpen=3)
/images/v1/config with {"flexible_variants": true}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
✅ 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
❌ 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
This skill prevents 13+ documented issues.
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 })
});
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:
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;
}
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
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.
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
}
}
});
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:
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 } } });
}
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)}`;
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 } } });
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:
const MAX_MEGAPIXELS = 100;
if (width * height > MAX_MEGAPIXELS * 1_000_000) {
return new Response('Image too large', { status: 413 });
}
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
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
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 GPSCopy-paste ready code for common patterns:
Usage:
cp templates/upload-api-basic.ts src/upload.ts
# Edit with your account ID and API token
In-depth documentation Claude can load as needed:
When to load:
check-versions.sh - Verify API endpoints are current
Custom Domains: Serve from your domain via /cdn-cgi/imagedelivery/<HASH>/<ID>/<VARIANT> (requires domain on Cloudflare, proxied). Use Transform Rules for custom paths.
Batch API: High-volume uploads via batch.imagedelivery.net with batch tokens (Dashboard → Images → Batch API)
Webhooks: Notifications for Direct Creator Upload (Dashboard → Notifications → Webhooks). Payload includes imageId, status, metadata.
Symptoms: /cdn-cgi/image/... returns original image or 404
Solutions:
Symptoms: Access-Control-Allow-Origin error in browser console
Solutions:
multipart/form-data encoding (let browser set Content-Type)/direct_upload from browser; call from backendfile (not image)Symptoms: Cf-Resized: err=9403 in response headers
Solutions:
Symptoms: 403 Forbidden when accessing signed URL
Solutions:
requireSignedURLs=trueSymptoms: Upload returns 200 OK but image not in dashboard
Solutions:
draft: true in response (Direct Creator Upload)/images/v1/{id})Last Verified: 2026-01-09 API Version: v2 (direct uploads), v1 (standard uploads) Optional: @cloudflare/workers-types@4.20260108.0
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.