Resize with Cloudflare Workers
Using Image Resizing with Workers gives you powerful programmatic control over every image request.
Here are a few examples of the flexibility Workers give you:
- Use a custom URL scheme. Instead of specifying pixel dimensions in image URLs, use preset names such as
thumbnail
andlarge
. - Hide the actual location of the original image. You can store images in an external S3 bucket or a hidden folder on your server without exposing that information in URLs.
- Implement content negotiation. This is useful to adapt image sizes, formats and quality dynamically based on the device and condition of the network.
The resizing feature is accessed via the options of a fetch()
subrequest inside a Worker.
Fetch options
The fetch()
function accepts parameters in the second argument inside the {cf: {image: {…}}}
object.
anim
Whether to preserve animation frames from input files. Default is true
. Setting it to false
reduces animations to still images. This setting is recommended when enlarging images or processing arbitrary user content, because large GIF animations can weigh tens or even hundreds of megabytes. It is also useful to set anim:false
when using format:"json"
to get the response quicker without the number of frames. Example:
URL formatanim=false
Workerscf: {image: {anim: false}}
background
Background color to add underneath the image. Applies to images with transparency (for example, PNG) and images resized with fit=pad
. Accepts any CSS color, such as #RRGGBB
and rgba(…)
. Example:
URL formatbackground=%23RRGGBB
OR
background=red
Workerscf: {image: {background: "#RRGGBB"}}
blur
Blur radius between 1
(slight blur) and 250
(maximum). Be aware that you cannot use this option to reliably obscure image content, because savvy users can modify an image’s URL and remove the blur option. Use Workers to control which options can be set. Example:
URL formatblur=50
Workerscf: {image: {blur: 50}}
border
Adds a border around the image. The border is added after resizing. Border width takes dpr
into account, and can be specified either using a single width
property, or individually for each side.
Workerscf: {image: {border: {color: "rgb(0,0,0,0)", top: 5, right: 10, bottom: 5, left: 10}}}
cf: {image: {border: {color: "#FFFFFF", width: 10}}}
brightness
Increase brightness by a factor. A value of 1.0
equals no change, a value of 0.5
equals half brightness, and a value of 2.0
equals twice as bright. 0
is ignored. Example:
URL formatbrightness=0.5
Workerscf: {image: {brightness: 0.5}}
compression=fast
Slightly reduces latency on a cache miss by selecting a quickest-to-compress file format, at a cost of increased file size and lower image quality. It will usually override the format
option and choose JPEG over WebP or AVIF. We do not recommend using this option, except in unusual circumstances like resizing uncacheable dynamically-generated images.
URL formatcompression=fast
Workerscf: {image: {compression: "fast"}}
contrast
Increase contrast by a factor. A value of 1.0
equals no change, a value of 0.5
equals low contrast, and a value of 2.0
equals high contrast. 0
is ignored. Example:
URL formatcontrast=0.5
Workerscf: {image: {contrast: 0.5}}
dpr
Device Pixel Ratio. Default is 1
. Multiplier for width
/height
that makes it easier to specify higher-DPI sizes in <img srcset>
. Example:
URL formatdpr=1
Workerscf: {image: {dpr: 1}}
fit
Affects interpretation of width
and height
. All resizing modes preserve aspect ratio. Used as a string in Workers integration. Available modes are:
scale-down
Similar tocontain
, but the image is never enlarged. If the image is larger than givenwidth
orheight
, it will be resized. Otherwise its original size will be kept. Example:
URL formatfit=scale-down
Workerscf: {image: {fit: "scale-down"}}
contain
Image will be resized (shrunk or enlarged) to be as large as possible within the givenwidth
orheight
while preserving the aspect ratio. If you only provide a single dimension (for example, onlywidth
), the image will be shrunk or enlarged to exactly match that dimension. Example:
URL formatfit=contain
Workerscf: {image: {fit: "contain"}}
cover
Resizes (shrinks or enlarges) to fill the entire area ofwidth
andheight
. If the image has an aspect ratio different from the ratio ofwidth
andheight
, it will be cropped to fit. Example:
URL formatfit=cover
Workerscf: {image: {fit: "cover"}}
crop
Image will be shrunk and cropped to fit within the area specified bywidth
andheight
. The image will not be enlarged. For images smaller than the given dimensions, it is the same asscale-down
. For images larger than the given dimensions, it is the same ascover
. See alsotrim
. Example:
URL formatfit=crop
Workerscf: {image: {fit: "crop"}}
pad
Resizes to the maximum size that fits within the givenwidth
andheight
, and then fills the remaining area with abackground
color (white by default). This mode is not recommended, since you can achieve the same effect more efficiently with thecontain
mode and the CSSobject-fit: contain
property. Example:
URL formatfit=pad
Workerscf: {image: {fit: "pad"}}
format
The auto
option will serve the WebP or AVIF format to browsers that support it. If this option is not specified, a standard format like JPEG or PNG will be used.
Workers integration supports:
avif
: Generate images in AVIF format if possible (with WebP as a fallback).webp
: Generate images in Google WebP format. Set the quality to100
to get the WebP lossless format.json
: Instead of generating an image, outputs information about the image in JSON format. The JSON object will contain data such as image size (before and after resizing), source image’s MIME type, and file size.
Example:
URL formatformat=auto
Workerscf: {image: {format: "avif"}}
For the format:auto
option to work with a custom Worker, you need to parse the Accept
header. Refer to this example Worker for a complete overview of how to set up an Image Resizing Worker.
Custom Worker for Image Resizing with `format:auto`const accept = request.headers.get("accept");
let image = {};
if (/image\/avif/.test(accept)) { image.format = "avif";
} else if (/image\/webp/.test(accept)) { image.format = "webp";
}
return fetch(url, {cf:{image}});
gamma
Increase exposure by a factor. A value of 1.0
equals no change, a value of 0.5
darkens the image, and a value of 2.0
lightens the image. 0
is ignored. Example:
URL formatgamma=0.5
Workerscf: {image: {gamma: 0.5}}
gravity
When cropping with fit: "cover"
and fit: "crop"
, this parameter defines the side or point that should not be cropped. Available options are:
auto
Selects focal point based on saliency detection (using maximum symmetric surround algorithm). Example:
URL formatgravity=auto
Workerscf: {image: {gravity: "auto"}}
side
A side ("left"
,"right"
,"top"
,"bottom"
) or coordinates specified on a scale from0.0
(top or left) to1.0
(bottom or right),0.5
being the center. The X and Y coordinates are separated by lowercasex
in the URL format. For example,0x1
means left and bottom,0.5x0.5
is the center,0.5x0.33
is a point in the top third of the image.For the Workers integration, use an object
{x, y}
to specify coordinates. It contains focal point coordinates in the original image expressed as fractions ranging from0.0
(top or left) to1.0
(bottom or right), with0.5
being the center.{fit: "cover", gravity: {x:0.5, y:0.2}}
will crop each side to preserve as much as possible around a point at 20% of the height of the source image. Example:
URL formatgravity=left
or
gravity=0x1
Workerscf: {image: {gravity: "right"}}
or
cf: {image: {gravity: {x:0.5, y:0.2}}}
height
Specifies maximum height of the image in pixels. Exact behavior depends on the fit
mode (described below). Example:
URL formatheight=250
Workerscf: {image: {height: 250}}
metadata
Controls amount of invisible metadata (EXIF data) that should be preserved. Color profiles and EXIF rotation are applied to the image even if the metadata is discarded. Note that if the Polish feature is enabled, all metadata may have been removed already and this option will have no effect. Options are:
keep
Preserves most of EXIF metadata, including GPS location if present. Example:
URL formatmetadata=keep
Workerscf: {image: {metadata: "keep"}}
copyright
Discard all metadata except EXIF copyright tag. This is the default behavior for JPEG images. Example:
URL formatmetadata=copyright
Workerscf: {image: {metadata: "copyright"}}
none
Discard all invisible EXIF metadata. Currently, WebP and PNG output formats always discard metadata. Example:
URL formatmetadata=none
Workerscf: {image: {metadata: "none"}}
onerror=redirect
In case of a fatal error that prevents the image from being resized, redirects to the unresized source image URL. This may be useful in case some images require user authentication and cannot be fetched anonymously via Worker. This option should not be used if there is a chance the source image is very large. This option is ignored if the image is from another domain, but you can use it with subdomains. Example:
URL formatonerror=redirect
Workerscf: {image: {onerror: "redirect"}}
quality
Specifies quality for images in JPEG, WebP, and AVIF formats. The quality is in a 1-100 scale, but useful values are between 50
(low quality, small file size) and 90
(high quality, large file size). 85
is the default. When using the PNG format, an explicit quality setting allows use of PNG8 (palette) variant of the format. Example:
URL formatquality=50
Workerscf: {image: {quality: 50}}
rotate
Number of degrees (90
, 180
, or 270
) to rotate the image by. width
and height
options refer to axes after rotation. Example:
URL formatrotate=90
Workerscf: {image: {rotate: 90}}
sharpen
Specifies strength of sharpening filter to apply to the image. The value is a floating-point number between 0
(no sharpening, default) and 10
(maximum). 1
is a recommended value for downscaled images. Example:
URL formatsharpen=2
Workerscf: {image: {sharpen: 2}}
trim
Specifies a number of pixels to cut off on each side. Allows removal of borders or cutting out a specific fragment of an image. Trimming is performed before resizing or rotation. Takes dpr
into account. For Image Resizing and Cloudflare Images, use as four numbers in pixels separated by a semicolon, in the form of top;right;bottom;left
. For the Workers integration, specify an object with four properties: {top, right, bottom, left}
. Example:
URL formattrim=20;30;20;0
Workerscf: {image: {trim: {"top": 12, "right": 78, "bottom": 34, "left": 56,}}}
width
Specifies maximum width of the image in pixels. Exact behavior depends on the fit
mode (described below). Example:
URL formatwidth=250
Workerscf: {image: {width: 250}}
In your worker, where you would fetch the image using fetch(request)
, add options like in the following example:
fetch(imageURL, { cf: { image: { fit: "scale-down", width: 800, height: 600 } }
})
These typings are also available in our Workers TypeScript definitions library.
Configure a Worker
Create a new script in the Workers section of the Cloudflare dashboard. Scope your Worker script to a path dedicated to serving assets, such as /images/*
or /assets/*
. Only supported image formats can be resized. Attempting to resize any other type of resource (CSS, HTML) will result in an error.
It is best to keep the path handled by the Worker separate from the path to original (unresized) images, to avoid request loops caused by the image resizing worker calling itself. For example, store your images in example.com/originals/
directory, and handle resizing via example.com/thumbnails/*
path that fetches images from the /originals/
directory. If source images are stored in a location that is handled by a Worker, you must prevent the Worker from creating an infinite loop.
Prevent request loops
To perform resizing and optimizations, the Worker must be able to fetch the original, unresized image from your origin server. If the path handled by your Worker overlaps with the path where images are stored on your server, it could cause an infinite loop by the Worker trying to request images from itself.
You must detect which requests must go directly to the origin server. When the image-resizing
string is present in the Via
header, it means that it is a request coming from another Worker and should be directed to the origin server:
addEventListener("fetch", event => { // If this request is coming from image resizing worker, // avoid causing an infinite loop by resizing it again: if (/image-resizing/.test(event.request.headers.get("via"))) { return fetch(event.request) }
// Now you can safely use image resizing here
}
Lack of preview in the dashboard
The script preview of the Worker editor ignores fetch()
options, and will always fetch unresized images. To see the effect of Image Resizing you must deploy the Worker script and use it outside of the editor.
Error handling
When an image cannot be resized — for example, because the image does not exist or the resizing parameters were invalid — the response will have an HTTP status indicating an error (for example, 400
, 404
, or 502
).
By default, the error will be forwarded to the browser, but you can decide how to handle errors. For example, you can redirect the browser to the original, unresized image instead:
const response = await fetch(imageURL, options)
if (response.ok || response.redirected) { // fetch() may respond with status 304 return response} else { return response.redirect(imageURL, 307)
}
Keep in mind that if the original images on your server are very large, it may be better not to display failing images at all, than to fall back to overly large images that could use too much bandwidth, memory, or break page layout.
You can also replace failed images with a placeholder image:
const response = await fetch(imageURL, options)
if (response.ok || response.redirected) { return response} else { // Change to a URL on your server return fetch("https://img.example.com/blank-placeholder.png")
}
An example worker
Assuming you set up a Worker on https://example.com/image-resizing
to handle URLs like https://example.com/image-resizing?width=80&image=https://example.com/uploads/avatar1.jpg
:
addEventListener("fetch", event => { event.respondWith(handleRequest(event.request))
})
/** * Fetch and log a request * @param {Request} request */
async function handleRequest(request) { // Parse request URL to get access to query string let url = new URL(request.url)
// Cloudflare-specific options are in the cf object. let options = { cf: { image: {} } }
// Copy parameters from query string to request options. // You can implement various different parameters here. if (url.searchParams.has("fit")) options.cf.image.fit = url.searchParams.get("fit") if (url.searchParams.has("width")) options.cf.image.width = url.searchParams.get("width") if (url.searchParams.has("height")) options.cf.image.height = url.searchParams.get("height") if (url.searchParams.has("quality")) options.cf.image.quality = url.searchParams.get("quality")
// Your Worker is responsible for automatic format negotiation. Check the Accept header. const accept = request.headers.get("Accept"); if (/image\/avif/.test(accept)) { options.cf.image.format = 'avif'; } else if (/image\/webp/.test(accept)) { options.cf.image.format = 'webp'; }
// Get URL of the original (full size) image to resize. // You could adjust the URL here, e.g., prefix it with a fixed address of your server, // so that user-visible URLs are shorter and cleaner. const imageURL = url.searchParams.get("image") if (!imageURL) return new Response('Missing "image" value', { status: 400 })
try { // TODO: Customize validation logic const { hostname, pathname } = new URL(imageURL)
// Optionally, only allow URLs with JPEG, PNG, GIF, or WebP file extensions // @see https://developers.cloudflare.com/images/url-format#supported-formats-and-limitations if (!/\.(jpe?g|png|gif|webp)$/i.test(pathname)) { return new Response('Disallowed file extension', { status: 400 }) }
// Demo: Only accept "example.com" images if (hostname !== 'example.com') { return new Response('Must use "example.com" source images', { status: 403 }) } } catch (err) { return new Response('Invalid "image" value', { status: 400 }) }
// Build a request that passes through request headers const imageRequest = new Request(imageURL, { headers: request.headers })
// Returning fetch() with resizing options will pass through response with the resized image. return fetch(imageRequest, options)
}
When testing image resizing, please deploy the script first. Resizing will not be active in the online editor in the dashboard.
Warning about cacheKey
Resized images are always cached. They are cached as additional variants under a cache entry for the URL of the full-size source image in the fetch
subrequest. Do not worry about using many different Workers or many external URLs — they do not influence caching of resized images, and you do not need to do anything for resized images to be cached correctly.
If you use the cacheKey
fetch option to unify caches of multiple different source URLs, you must not add any resizing options to the cacheKey
, as this will fragment the cache and hurt caching performance. The cacheKey
option is meant for the full-size source image URL only, not for its resized variants.