Images API
The @cloudwerk/images package provides integration with Cloudflare’s image services: Hosted Images for storage and delivery, Image Transformer for CDN-style resizing, and the IMAGES binding for transform pipelines.
Installation
Section titled “Installation”pnpm add @cloudwerk/imagesCloudflare Image Products
Section titled “Cloudflare Image Products”Cloudflare offers three distinct image services, each suited for different use cases:
| Product | Purpose | Requirements |
|---|---|---|
| Hosted Images | Store & serve images via CDN | Account ID, API token, paid plan |
| Image Transformer | Resize images via URL params | Zone-level route, paid plan |
| IMAGES Binding | Transform pipeline in Worker | wrangler.toml binding, paid plan |
Hosted Images
Section titled “Hosted Images”defineImage()
Section titled “defineImage()”Creates an image definition with named variants.
import { defineImage } from '@cloudwerk/images'
export default defineImage({ variants: Record<string, ImageVariant>, accountId?: string | EnvRef, apiToken?: string | EnvRef,})Parameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
variants | Record<string, ImageVariant> | Named transformation variants |
accountId | string | EnvRef | Cloudflare account ID (default: { env: 'CF_ACCOUNT_ID' }) |
apiToken | string | EnvRef | Cloudflare API token (default: { env: 'CF_IMAGES_TOKEN' }) |
Returns
Section titled “Returns”Returns an ImageDefinition object that Cloudwerk registers automatically.
ImageVariant
Section titled “ImageVariant”Transformation options for a variant.
interface ImageVariant { width?: number // Width in pixels height?: number // Height in pixels fit?: 'cover' | 'contain' | 'scale-down' | 'crop' | 'pad' format?: 'webp' | 'avif' | 'jpeg' | 'png' | 'auto' quality?: number // 1-100 dpr?: number // Device pixel ratio (1-3) gravity?: 'auto' | 'center' | 'top' | 'bottom' | 'left' | 'right' | 'face' sharpen?: number // 0-10 blur?: number // 1-250 brightness?: number // -1 to 1 contrast?: number // -1 to 1 rotate?: 0 | 90 | 180 | 270 metadata?: 'keep' | 'copyright' | 'none'}Example
Section titled “Example”// app/images/avatars.tsimport { defineImage } from '@cloudwerk/images'
export default defineImage({ variants: { thumbnail: { width: 100, height: 100, fit: 'cover' }, profile: { width: 400, height: 400, fit: 'cover' }, large: { width: 800, fit: 'scale-down', quality: 90 }, },})EnvRef
Section titled “EnvRef”Reference environment variables for credentials.
interface EnvRef { env: string // Environment variable name}
// Exampleexport default defineImage({ variants: { /* ... */ }, accountId: { env: 'CLOUDFLARE_ACCOUNT_ID' }, apiToken: { env: 'CLOUDFLARE_IMAGES_TOKEN' },})ImageClient
Section titled “ImageClient”The image client provides methods for uploading, retrieving, and managing images.
images Proxy
Section titled “images Proxy”Access image clients via the images proxy.
import { images } from '@cloudwerk/core/bindings'
// Upload an imageconst result = await images.avatars.upload(file)
// Get variant URLconst url = images.avatars.url(result.id, 'thumbnail')upload()
Section titled “upload()”Upload an image and receive an image ID.
const result = await images.avatars.upload(file: File | Blob | ArrayBuffer, options?: UploadOptions)UploadOptions
Section titled “UploadOptions”interface UploadOptions { id?: string // Custom image ID (auto-generated if not provided) metadata?: Record<string, string> // Custom metadata requireSignedUrls?: boolean // Require signed URLs}Returns
Section titled “Returns”interface ImageResult { id: string // Image ID filename?: string // Original filename metadata?: Record<string, string> // Custom metadata uploaded: Date // Upload timestamp variants: string[] // Available variant names}Get the URL for an image variant.
const url = images.avatars.url(id: string, variant: string): stringExample
Section titled “Example”import { images } from '@cloudwerk/core/bindings'import { json } from '@cloudwerk/core'
export async function POST(request: Request) { const formData = await request.formData() const file = formData.get('avatar') as File
const result = await images.avatars.upload(file, { metadata: { userId: 'user-123' }, })
return json({ id: result.id, thumbnail: images.avatars.url(result.id, 'thumbnail'), profile: images.avatars.url(result.id, 'profile'), })}getDirectUploadUrl()
Section titled “getDirectUploadUrl()”Generate a URL for direct browser uploads.
const directUpload = await images.avatars.getDirectUploadUrl(options?: DirectUploadOptions)DirectUploadOptions
Section titled “DirectUploadOptions”interface DirectUploadOptions { id?: string // Custom image ID metadata?: Record<string, string> // Custom metadata requireSignedUrls?: boolean // Require signed URLs expiry?: Date // URL expiration}Returns
Section titled “Returns”interface DirectUploadResult { id: string // Image ID uploadUrl: string // URL for browser upload}list()
Section titled “list()”List uploaded images.
const images = await images.avatars.list(options?: ListOptions)ListOptions
Section titled “ListOptions”interface ListOptions { page?: number // Page number (1-based) perPage?: number // Results per page (max 1000)}delete()
Section titled “delete()”Delete an image.
await images.avatars.delete(id: string)Get image details.
const details = await images.avatars.get(id: string)Image Transformer
Section titled “Image Transformer”createImageTransformer()
Section titled “createImageTransformer()”Creates a route handler for CDN-style image resizing using Cloudflare’s Image Resizing service.
import { createImageTransformer } from '@cloudwerk/images'
export const GET = createImageTransformer(config?: TransformerConfig)TransformerConfig
Section titled “TransformerConfig”interface TransformerConfig { allowedOrigins?: string[] // Allowed image source origins presets?: Record<string, TransformPreset> // Named presets defaults?: TransformPreset // Default transformations maxWidth?: number // Max width (default: 4096) maxHeight?: number // Max height (default: 4096) cacheControl?: string // Cache header (default: 1 year) allowArbitrary?: boolean // Allow arbitrary params (default: true) validateSource?: (url: URL) => boolean | Promise<boolean> // Custom validation}TransformPreset
Section titled “TransformPreset”interface TransformPreset { width?: number height?: number fit?: 'cover' | 'contain' | 'scale-down' | 'crop' | 'pad' format?: 'webp' | 'avif' | 'jpeg' | 'png' | 'auto' quality?: number // 1-100 dpr?: number // 1-3 gravity?: 'auto' | 'center' | 'top' | 'bottom' | 'left' | 'right' | 'face' sharpen?: number // 0-10 blur?: number // 1-250 brightness?: number // -1 to 1 contrast?: number // -1 to 1 rotate?: 0 | 90 | 180 | 270 metadata?: 'keep' | 'copyright' | 'none' background?: string // Hex or RGB for padding}Example
Section titled “Example”// app/cdn/images/[...path]/route.tsimport { createImageTransformer } from '@cloudwerk/images'
export const GET = createImageTransformer({ allowedOrigins: ['https://images.mysite.com'], presets: { thumbnail: { width: 100, height: 100, fit: 'cover' }, hero: { width: 1920, height: 1080, fit: 'cover' }, }, defaults: { format: 'auto', quality: 85, },})URL Format
Section titled “URL Format”The transformer parses the source URL from the path and options from query params:
/cdn/images/https://images.mysite.com/photo.jpg?preset=thumbnail/cdn/images/https://images.mysite.com/photo.jpg?w=800&h=600&fit=coverQuery Parameters
Section titled “Query Parameters”| Parameter | Alias | Description |
|---|---|---|
preset | - | Named preset from config |
width | w | Width in pixels |
height | h | Height in pixels |
fit | - | Fit mode |
format | f | Output format |
quality | q | Quality (1-100) |
dpr | - | Device pixel ratio |
gravity | - | Crop gravity |
blur | - | Blur amount |
sharpen | - | Sharpen amount |
brightness | - | Brightness adjustment |
contrast | - | Contrast adjustment |
rotate | - | Rotation degrees |
variantToPreset()
Section titled “variantToPreset()”Convert an ImageVariant to a TransformPreset for reusing hosted image variants.
import { variantToPreset, createImageTransformer } from '@cloudwerk/images'import avatars from '../images/avatars'
const presets = Object.fromEntries( Object.entries(avatars.variants).map(([name, variant]) => [ name, variantToPreset(variant), ]))
export const GET = createImageTransformer({ presets })IMAGES Binding
Section titled “IMAGES Binding”The IMAGES binding provides in-Worker image transformation without uploading to Cloudflare Images.
Configuration
Section titled “Configuration”Add the binding to wrangler.toml:
[[images]]binding = "MY_IMAGES"CloudflareImagesBinding
Section titled “CloudflareImagesBinding”interface CloudflareImagesBinding { input(source: Blob | ArrayBuffer | ReadableStream<Uint8Array>): CloudflareImagesTransformBuilder info(source: Blob | ArrayBuffer | ReadableStream<Uint8Array>): Promise<CloudflareImageInfo>}
interface CloudflareImageInfo { width: number height: number format: string fileSize: number}CloudflareImagesTransformBuilder
Section titled “CloudflareImagesTransformBuilder”Fluent API for chaining transformations.
interface CloudflareImagesTransformBuilder { transform(options: CloudflareImageTransformOptions): CloudflareImagesTransformBuilder output(options: CloudflareImageOutputOptions): CloudflareImagesTransformBuilder response(): Promise<Response> blob(): Promise<Blob> arrayBuffer(): Promise<ArrayBuffer> stream(): ReadableStream<Uint8Array>}Example
Section titled “Example”import { getBinding } from '@cloudwerk/core/bindings'import type { CloudflareImagesBinding } from '@cloudwerk/images'
export async function POST(request: Request) { const IMAGES = getBinding<CloudflareImagesBinding>('MY_IMAGES')
const formData = await request.formData() const file = formData.get('image') as File
// Transform the image const response = await IMAGES .input(await file.arrayBuffer()) .transform({ width: 800, rotate: 90 }) .output({ format: 'image/webp', quality: 85 }) .response()
return response}CloudflareImageTransformOptions
Section titled “CloudflareImageTransformOptions”interface CloudflareImageTransformOptions { width?: number height?: number fit?: 'scale-down' | 'contain' | 'cover' | 'crop' | 'pad' gravity?: 'auto' | 'left' | 'right' | 'top' | 'bottom' | 'center' | 'face' quality?: number // 1-100 dpr?: number // 1-3 rotate?: 0 | 90 | 180 | 270 sharpen?: number // 0-10 blur?: number // 1-250 brightness?: number // -1 to 1 contrast?: number // -1 to 1 background?: string // Hex or RGB border?: { color: string; width: number } trim?: { top?: number; right?: number; bottom?: number; left?: number }}CloudflareImageOutputOptions
Section titled “CloudflareImageOutputOptions”interface CloudflareImageOutputOptions { format?: 'image/jpeg' | 'image/png' | 'image/webp' | 'image/avif' | 'image/gif' quality?: number // 1-100 metadata?: 'keep' | 'copyright' | 'none'}IMAGE_PRESETS
Section titled “IMAGE_PRESETS”Built-in presets for common use cases.
import { IMAGE_PRESETS } from '@cloudwerk/images'
// Available presetsIMAGE_PRESETS.thumbnail // 100x100 coverIMAGE_PRESETS.preview // 400x400 containIMAGE_PRESETS.large // 1200px wideIMAGE_PRESETS.hero // 1920x1080 coverIMAGE_PRESETS.social // 1200x630 (Open Graph)IMAGE_PRESETS.mobile // 640px wide, WebPError Classes
Section titled “Error Classes”ImageError
Section titled “ImageError”Base error class for image operations.
import { ImageError } from '@cloudwerk/images'
class ImageError extends Error { readonly code: string}ImageConfigError
Section titled “ImageConfigError”Invalid image configuration.
class ImageConfigError extends ImageError { readonly code: 'IMAGE_CONFIG_ERROR'}ImageUploadError
Section titled “ImageUploadError”Image upload failed.
class ImageUploadError extends ImageError { readonly code: 'IMAGE_UPLOAD_ERROR' readonly status?: number}ImageNotFoundError
Section titled “ImageNotFoundError”Image not found.
class ImageNotFoundError extends ImageError { readonly code: 'IMAGE_NOT_FOUND' readonly imageId: string}ImageVariantError
Section titled “ImageVariantError”Invalid variant name.
class ImageVariantError extends ImageError { readonly code: 'IMAGE_VARIANT_ERROR' readonly variantName: string readonly availableVariants: string[]}ImageTransformError
Section titled “ImageTransformError”Image transformation failed.
class ImageTransformError extends ImageError { readonly code: string readonly status: number}Image Utilities
Section titled “Image Utilities”getImages()
Section titled “getImages()”Get a typed image client by name.
import { getImages } from '@cloudwerk/core/bindings'
const avatars = getImages('avatars')await avatars.upload(file)hasImages()
Section titled “hasImages()”Check if an image definition exists.
import { hasImages } from '@cloudwerk/core/bindings'
if (hasImages('avatars')) { await images.avatars.upload(file)}getImagesNames()
Section titled “getImagesNames()”List all available image definition names.
import { getImagesNames } from '@cloudwerk/core/bindings'
const available = getImagesNames()// ['avatars', 'products', 'banners']Type Definitions
Section titled “Type Definitions”ImageDefinition
Section titled “ImageDefinition”interface ImageDefinition<V extends Record<string, ImageVariant>> { variants: V accountId?: string | EnvRef apiToken?: string | EnvRef}ImageClientInterface
Section titled “ImageClientInterface”interface ImageClientInterface<V extends string = string> { upload(source: File | Blob | ArrayBuffer, options?: UploadOptions): Promise<ImageResult> url(id: string, variant: V): string get(id: string): Promise<ImageResult> delete(id: string): Promise<void> list(options?: ListOptions): Promise<ImageResult[]> getDirectUploadUrl(options?: DirectUploadOptions): Promise<DirectUploadResult>}Next Steps
Section titled “Next Steps”- Images Guide - Patterns and best practices
- Bindings API - Core binding utilities
- File Conventions -
app/images/structure