How can I provide user-friendly image download URLs

We’ve been using the Images service for the past year and it seems to work fine with web browsers. But we have customers who need to download images from our site (this is a key part of our business).

These are mostly non-technical customers and however they are trying to download the images, it seems that their software cannot handle the default .avif or .webp formats. How can I provide them with a URL that will let them download images in .jpg or .png format? It would be best if we could give them URLs with .jpg or .png suffixes.

I’ve looked at the information on the link below, which indicates that there should be a way to do this. Unfortunately, it does not give any specific examples and is not very helpful:

Hi, one suggestion would be is to use Cloudflare Worker in front of the Images.

And rewrite accept header on the request and add Content-Disposition header on the response, something like that:

export default {
	async fetch(request) {
		// You can find this in the dashboard, it should look something like this: ZWd9g1K7eljCn_KDTu_MWA
		const accountHash = '';

		const { pathname } = new URL(request.url);

		const headers = new Headers(request.headers)
		headers.set('Accept', 'image/jpeg, image/png')

		// A request to something like
		// will fetch "<accountHash>/83eb7b2-5392-4565-b69e-aff66acddd00/public"
		const response = await fetch(`${accountHash}${pathname}`, {
			method: 'GET',
			headers: headers
        const filename = pathname.substring(1);
		response.headers.set('Content-Disposition', `attachment; filename="${filename}"`);
		return response
1 Like

Thanks, that’s what I was looking for. Now I need to dust off my Worker skills and build upon your idea.

I took your example and come up with the worker script below.

Now I can fetch a Cloudflare-stored image like and it will return it in JPG format. For PNG format, just fetch with a .png extension, etc.

addEventListener('fetch', event => {

async function handleRequest(request) {
    // You can find this in the dashboard, it should look something like this: ZWd9g1K7eljCn_KDTu_MWA
    const accountHash = 'xxxxxxxx';
    let imageUrl = '' + accountHash + '/';

    const { pathname  } = new URL(request.url);
    let filename = pathname.substring(1);

    let extension = filename.split(/[#?]/)[0].split('.').pop().trim();
    if (extension === filename) {
        extension = '';
    extension = extension.toLowerCase();
    filename = filename.replace(/my_images_dir\//, '');

    if (extension) {
        const baseFilename = filename.substring(0, filename.length - extension.length - 1);
        imageUrl += baseFilename;
        filename = baseFilename + '.' + extension;
    } else {
        imageUrl += filename;

    const headers = new Headers(request.headers);

    // set headers to only accept image type matching the extension
    switch (extension) {
    case 'avif':
        headers.set('Accept', 'image/avif');
    case 'jpg':
    case 'jpeg':
        headers.set('Accept', 'image/jpeg');
    case 'png':
        headers.set('Accept', 'image/png');
    case 'webp':
        headers.set('Accept', 'image/webp');

    return await fetch(imageUrl, {
        method: 'GET',
        headers: headers

You might not need a worker. If you set the “Accept” header to the desired format, you can provide a download option for you users.

curl -H “Accept: image/jpg” -o image.jpg

If our users were more technical and were not using brain-dead software, I would not have have to create a worker. However, they’re not. So I just created a worker that sets the proper “Accept” header.

Since I did that, all the image download issues have disappeared. They had been trying to figure out the problem for about a month before someone contacted me about the issue.

1 Like

Very pleased to hear you got the Worker-based solution working to rewrite the Accept headers.

Note: that we now support auto-negotiation of what image format to serve to users. If an Accept header is sent that doesn’t indicate avif or webp support we will fallback to serving the original format:

1 Like