Install
$ agentstack add skill-jackspace-claudeskillz-cloudflare-images ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
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//images/v1 \
--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
//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:
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 {
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 " \
--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 " \
--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):
Upload
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:///cdn-cgi/image//
Example:
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:
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 {
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 " \
--header "Content-Type: application/json" \
--data '{
"id": "avatar",
"options": {
"fit": "cover",
"width": 200,
"height": 200,
"metadata": "none"
},
"neverRequireSignedURLs": false
}'
Use in URLs:
//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 " \
--header "Content-Type: application/json" \
--data '{"flexible_variants": true}'
Use in URLs:
//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///?exp=&sig=
Generate Signature (Workers example):
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:
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:
//mobile 480w,
https://imagedelivery.net///tablet 768w,
https://imagedelivery.net///desktop 1920w
"
sizes="(max-width: 480px) 480px, (max-width: 768px) 768px, 1920px"
src="https://imagedelivery.net///desktop"
alt="Responsive image"
/>
Using Flexible Variants:
//w=480,f=auto 480w,
https://imagedelivery.net///w=768,f=auto 768w,
https://imagedelivery.net///w=1920,f=auto 1920w
"
sizes="(max-width: 480px) 480px, (max-width: 768px) 768px, 1920px"
src="https://imagedelivery.net///w=1920,f=auto"
alt="Responsive image"
/>
Art Direction (different crops for mobile vs desktop):
//mobile-square"
/>
//desktop-wide"
/>
//desktop-wide" alt="Hero image" />
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 f
…
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [jackspace](https://github.com/jackspace)
- **Source:** [jackspace/ClaudeSkillz](https://github.com/jackspace/ClaudeSkillz)
- **License:** MIT
- **Homepage:** http://claudeskillz.jackspace.com/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.