INTEGRITY Cloudflare Docs

Manage hosted images with Workers

A binding connects your Worker to external resources on the Developer Platform, like Images, R2 buckets, or KV namespaces.

When managing hosted images, the Images binding lets your Worker upload, list, retrieve, update, and delete hosted images without calling the REST API directly. The hosted namespace exposes storage and management operations. This binding can also be used to optimize hosted images.

Bindings can be configured in the Cloudflare dashboard for your Worker or in the Wrangler configuration file in your project's directory.

Setup

To bind Images to your Worker, add the following to your Wrangler configuration file:

{
	"images": {
		"binding": "IMAGES", // available in your Worker on env.IMAGES
	},
}
[images]
binding = "IMAGES"

Within your Worker code, you can manage hosted images using the env.IMAGES.hosted namespace.

Methods

The env.IMAGES.hosted namespace lets you upload and list images across your account. To manage a specific image, call .image(imageId) to get a handle, then call a method on it.

.upload(image, options)

Uploads a new image to your account. You can pass image bytes as a stream or an ArrayBuffer. Returns ImageMetadata.

Accepts the following options as an ImageUploadOptions object:

.list(options)

Lists images in your account with pagination. Returns ImageList.

Accepts the following options as an ImageListOptions object:

.image(imageId)

Returns a handle for a single hosted image. The imageId can be the Cloudflare-generated UUID or a custom ID.

The handle itself does not make a network request, so it is cheap to construct.

.image(imageId).details()

Gets the metadata for an image. Returns ImageMetadata or null if no image with the given ID exists.

.image(imageId).bytes()

Gets the raw bytes of an image. Returns ReadableStream<Uint8Array> or null if no image with the given ID exists. This streams the original uploaded file. Pass the image bytes to .input() to optimize before serving, or use the URLs returned in ImageMetadata.variants or the image delivery URL to serve a predefined variant.

.image(imageId).update(options)

Updates the metadata or access controls for an image. All fields are optional; only the specified fields will be changed. Returns ImageMetadata with the updated values.

Accepts the following options as an ImageUpdateOptions object:

.image(imageId).delete()

Deletes an image. Returns true if the image was deleted or false if no image with the given ID existed.

Examples

Upload an image from a request body

export default {
	async fetch(request, env) {
		if (!request.body) {
			return new Response("Missing body", { status: 400 });
		}

		const image = await env.IMAGES.hosted.upload(request.body, {
			filename: "upload.jpg",
			metadata: { source: "worker" },
			requireSignedURLs: false,
		});

		return Response.json(image);
	},
};
export default {
	async fetch(request, env) {
		if (!request.body) {
			return new Response("Missing body", { status: 400 });
		}

		const image = await env.IMAGES.hosted.upload(request.body, {
			filename: "upload.jpg",
			metadata: { source: "worker" },
			requireSignedURLs: false,
		});

		return Response.json(image);
	},
};

Upload a base64-encoded image

Set encoding: "base64" and the binding will decode the body for you before uploading.

export default {
	async fetch(request, env) {
		if (!request.body) {
			return new Response("Missing body", { status: 400 });
		}

		const image = await env.IMAGES.hosted.upload(request.body, {
			encoding: "base64",
			filename: "upload.png",
		});

		return Response.json(image);
	},
};
export default {
	async fetch(request, env) {
		if (!request.body) {
			return new Response("Missing body", { status: 400 });
		}

		const image = await env.IMAGES.hosted.upload(request.body, {
			encoding: "base64",
			filename: "upload.png",
		});

		return Response.json(image);
	},
};

List images with pagination

export default {
	async fetch(request, env) {
		let cursor;
		const ids = [];

		do {
			const page = await env.IMAGES.hosted.list({ limit: 100, cursor });
			ids.push(...page.images.map((image) => image.id));
			cursor = page.cursor;
		} while (cursor);

		return Response.json({ count: ids.length, ids });
	},
};
export default {
	async fetch(request, env) {
		let cursor: string | undefined;
		const ids: string[] = [];

		do {
			const page = await env.IMAGES.hosted.list({ limit: 100, cursor });
			ids.push(...page.images.map((image) => image.id));
			cursor = page.cursor;
		} while (cursor);

		return Response.json({ count: ids.length, ids });
	},
};

Get the details for a single image

export default {
	async fetch(request, env) {
		const details = await env.IMAGES.hosted.image("IMAGE_ID").details();
		if (!details) {
			return new Response("Not found", { status: 404 });
		}
		return Response.json(details);
	},
};
export default {
	async fetch(request, env) {
		const details = await env.IMAGES.hosted.image("IMAGE_ID").details();
		if (!details) {
			return new Response("Not found", { status: 404 });
		}
		return Response.json(details);
	},
};

Stream the original bytes for an image

export default {
	async fetch(request, env) {
		const bytes = await env.IMAGES.hosted.image("IMAGE_ID").bytes();
		if (!bytes) {
			return new Response("Not found", { status: 404 });
		}
		return new Response(bytes);
	},
};
export default {
	async fetch(request, env) {
		const bytes = await env.IMAGES.hosted.image("IMAGE_ID").bytes();
		if (!bytes) {
			return new Response("Not found", { status: 404 });
		}
		return new Response(bytes);
	},
};

Update image metadata

export default {
	async fetch(request, env) {
		const updated = await env.IMAGES.hosted.image("IMAGE_ID").update({
			metadata: { reviewed: true },
		});
		return Response.json(updated);
	},
};
export default {
	async fetch(request, env) {
		const updated = await env.IMAGES.hosted.image("IMAGE_ID").update({
			metadata: { reviewed: true },
		});
		return Response.json(updated);
	},
};

Delete an image

export default {
	async fetch(request, env) {
		const deleted = await env.IMAGES.hosted.image("IMAGE_ID").delete();
		return new Response(deleted ? "Deleted" : "Not found", {
			status: deleted ? 200 : 404,
		});
	},
};
export default {
	async fetch(request, env) {
		const deleted = await env.IMAGES.hosted.image("IMAGE_ID").delete();
		return new Response(deleted ? "Deleted" : "Not found", {
			status: deleted ? 200 : 404,
		});
	},
};

Ingest a remote image into Images storage

This example fetches an image from a remote URL, uploads it into your Images account, and returns the first variant URL.

export default {
	async fetch(request, env) {
		const upstream = await fetch("https://example.com/photo.jpg");
		if (!upstream.ok || !upstream.body) {
			return new Response("Upstream fetch failed", { status: 502 });
		}

		const image = await env.IMAGES.hosted.upload(upstream.body, {
			filename: "photo.jpg",
			metadata: { source: "example.com" },
		});

		return Response.json({
			id: image.id,
			variant: image.variants[0],
		});
	},
};
export default {
	async fetch(request, env) {
		const upstream = await fetch("https://example.com/photo.jpg");
		if (!upstream.ok || !upstream.body) {
			return new Response("Upstream fetch failed", { status: 502 });
		}

		const image = await env.IMAGES.hosted.upload(upstream.body, {
			filename: "photo.jpg",
			metadata: { source: "example.com" },
		});

		return Response.json({
			id: image.id,
			variant: image.variants[0],
		});
	},
};

Type definitions

ImageMetadata

Returned by operations that retrieve, create, or update an image.

ImageList

Returned by list().

Error handling

Methods that fail throw an ImagesError.upload(), .list(), .update() — with the following properties:

Methods that fetch a single image — .details(), .bytes(), and .delete() — return null or false for "not found" rather than throwing.

You may want to wrap operations that can throw in a try...catch block.

Local development

When you run wrangler dev, operations for managing hosted images are served by a local mock that stores images in an embedded KV namespace. The mock supports every method documented on this page, so you can develop and test your Worker offline.

The mock is only suitable for local development. To exercise the real Images service from your local environment, run wrangler dev --remote.