# Cloudflare image quota and WebP traps

> A free monthly quota of unique transformations, a fallback that hides it running out, and lossy WebP that greys thin text. How to keep the pipeline honest.

Published 2026-10-28 · https://aiskills402.com/blog/cloudflare-image-pipeline-quota-webp

An image pipeline on Cloudflare has two ways to go wrong without anyone noticing. It can run out of a shared monthly allowance of image transformations and fall back to serving the untouched originals, so pages get heavier while every status code stays green. Or it can encode pictures in a way that is smaller but quietly worse, for example lossy WebP applied to charts with thin coloured text. Neither produces an error in your logs. This guide explains how the allowance is counted, which settings hide its exhaustion, how to read what kind of WebP a file really is, and what to check before you ship.

## How the quota is counted

The free plan includes a monthly allowance of 5,000 distinct variants. A variant is one pairing of a source picture with its settings; asking for the same pairing again within that calendar month is counted once, and the `format` and `quality` settings are ignored when deciding whether two pairings differ ([Cloudflare Images pricing](https://developers.cloudflare.com/images/pricing/)). Once the limit is reached, new transformations return error 9422, while ones already in cache keep being served, and the free plan does not bill you for going over (same page).

Two consequences are easy to miss. The first is that the count is a product, not a sum, since each pairing of image and settings counts on its own ([Cloudflare Images pricing](https://developers.cloudflare.com/images/pricing/)). Every width you offer for every image is a separate combination, so a large archive with a long list of widths can pass the allowance on its own. Take 800 images and seven widths: that is 5,600 combinations against 5,000 allowed, by simple multiplication (our own example, not a figure from the page). The second is that switching the output format costs nothing in this count, so offering both WebP and a fallback does not double anything.

The practical rule is general practice, not a vendor statement: write down every width your layouts really use, give each one a reason, and delete the rest. Image components often ship with a generous default list of device sizes. Each entry in it is a promise to spend quota.

## The fallback that hides the problem

Cloudflare offers an `onerror=redirect` option on transformation URLs, which sends the visitor to the original image when the transformation fails. It applies only when the picture sits in the same zone as the page (subdomains count), and only for remote images fetched through the URL interface ([Cloudflare, transform via URL](https://developers.cloudflare.com/images/transform-images/transform-via-url/)). The pricing page also names it as the way to fall back to the original when the quota is gone ([Cloudflare Images pricing](https://developers.cloudflare.com/images/pricing/)).

Read that sentence from the point of view of a page. The quota runs out, the transformation is refused, the visitor is redirected to the full-size original, and the page still renders. Nothing fails. The only change is weight: pictures that were small are now as big as they were when uploaded. Because the failure is a redirect and not an error, ordinary uptime and error-rate monitors stay quiet.

Two defences are available. The first is to watch the transformation path itself: a sample request built like the real one but lacking the fallback option should return an image, and an answer carrying error 9422 means the allowance is spent. The `Cf-Resized` response header carries `err=` followed by a code when something went wrong ([Cloudflare troubleshooting](https://developers.cloudflare.com/images/reference/troubleshooting/)). The second is to ask whether you need the option at all. A pipeline that has stopped transforming, and serves finished files straight from storage, has no use for it.

## Do not ask for more pixels than exist

With `fit=scale-down`, a source smaller than the target box comes back at its own size ([Cloudflare, transform via URL](https://developers.cloudflare.com/images/transform-images/transform-via-url/)). Asking for something wider than the source therefore returns the same pixels under a different URL, and the counting rule above treats a different pairing of picture and settings as another variant ([Cloudflare Images pricing](https://developers.cloudflare.com/images/pricing/)). Joining those two pages is our own inference and was not tried on an account. If it holds, avoiding it is cheap: clamp the width you ask for to the source width, or hand out the source itself.

## Turning transformations off properly

Teams that hit the allowance often decide to stop transforming and serve their own pre-sized files. Then "off" has three places to be checked, because the switch is split between your code, your deployment settings and the dashboard. The image component option and the loader live in code. A leftover images binding in the deployment settings may still let a framework adapter transform for anyone who calls its optimizer address; that comes from our experience with such setups, not from the cited pages. And the dashboard setting for the zone is invisible to the code.

The last one can probably be probed without touching the allowance, though we did not check whether such a request is counted. Request a resized version of a picture that was never uploaded: if the response header shows error 9404, the zone is still switched on, because that error describes a missing image or a wrong URL ([Cloudflare troubleshooting](https://developers.cloudflare.com/images/reference/troubleshooting/)). A bare 404 suggests it is off. This reading of the two answers is our inference from the error text, so confirm it once on your own zone.

One more trap, from general practice and not from the cited pages: the free preview address that a Worker receives is not a domain of your own, so a test there may say little about the zone setting in your dashboard. Run the test on your own domain, which is a zone in your account.

## What kind of WebP is this file?

WebP has a lossy and a lossless mode, and a file announces which one it uses. The container starts with a twelve-byte file header, and the first chunk header follows immediately. The chunk is named `VP8 ` (with a trailing space) for lossy data, `VP8L` for lossless, and `VP8X` when the file carries extra features such as alpha, metadata or animation ([Google, WebP container](https://developers.google.com/speed/webp/docs/riff_container)). So a few bytes at a fixed place tell a script whether a stored file was already compressed with loss. In Cloudflare's own options, asking for quality 100 returns lossless WebP ([Cloudflare, transform via URL](https://developers.cloudflare.com/images/transform-images/transform-via-url/)).

This matters for two reasons. A job that walks your storage and re-encodes everything will happily compress an already lossy file a second time, and each pass loses a little more with no message. The safe habits are to encode once at ingest from the kept original, or to modify only the files whose first chunk says lossless. A `VP8X` file may be an animation or have transparency, so it belongs to neither plain group.

## Photographs and graphics need different answers

Lossy encoders are tuned for photographs. They also reduce colour detail, and that is what hurts a chart or an infographic whose meaning sits in thin coloured text: the letters drift towards grey. We saw this on one infographic of our own, but that was a single example and not a measurement, so we do not give figures here. The general practice that follows is to decide lossy or lossless per file by comparing the sizes of both encodings of the same picture. A photograph is usually much larger as lossless, a flat graphic is not much different, and the gap between the two behaviours makes a threshold easy to choose. Do not pick the format from a file name or an extension, since PNG is simply what many generators return.

For the same reason, choose the stored format at a single point of entry that all uploads pass through, rather than letting each path keep whatever bytes arrived. A photograph stored as PNG is, by general experience, many times larger than the same picture as lossy WebP or JPEG.

## Do not overwrite the original address

When you recompress or rename a file, write the new bytes under a new key, for example one that includes a short hash of the content. Overwriting the old key means caches disagree about what lives there. Cloudflare does not purge optimized images one by one; purging the original's URL clears its optimized versions along with it ([Cloudflare, transform via URL](https://developers.cloudflare.com/images/transform-images/transform-via-url/)). A new key avoids the purge question entirely, and the old address keeps working while links and search results catch up.

## A check that can fail

Run this before release and again after any change to widths or storage:

1. List every width the pipeline can request and the reason for each. Multiply by the number of images. Compare with the monthly allowance, and then with the real usage figure from your dashboard.
2. Fetch one resized picture without the fallback option. It must return an image and not the quota error ([Cloudflare troubleshooting](https://developers.cloudflare.com/images/reference/troubleshooting/)).
3. Look for any place where your code could build a URL wider than the stored source, and make sure none exists.
4. Fetch a resized version of a picture that was never uploaded and read the header, so you know whether the zone is switched on.
5. Read the first chunk name of a few stored WebP files. A script that re-encodes should skip the lossy ones.
6. Open a chart or infographic from the output and look at the thin coloured text at full size.

The check fails when step 2 gives the quota error, or when step 6 shows grey where there was colour. Break each step on purpose once, for example with a test zone that has no allowance left, to see that it can fail. A check that never fails tells you nothing.

## When this does not apply

The text above concerns the free plan's allowance and the transformation path built from URLs. On a paid plan the limits and billing differ, and the vendor page is where to read them. A site that serves only finished files from storage, and never builds a transformation address, can skip the quota and fallback sections; the format and key advice still holds. Reading error 9404 as an on-off signal (the [troubleshooting page](https://developers.cloudflare.com/images/reference/troubleshooting/) only describes it as a missing image), and the idea that a wider request creates another variant, are our own interpretation of the documentation and were not tested here. Vendors change behaviour, so reread the cited pages before depending on a detail. We ran no new experiment for this guide, our infographic observation is one case, and your images may behave differently.

Whether anyone benefits from lighter pages is a separate question, and our piece on [counting real readers in Cloudflare Web Analytics](/blog/cloudflare-web-analytics-real-readers) shows how to tell people from bots before you judge a change.

## Reviewing a pipeline

For a review before release, the [image-pipeline-review](https://aiskills402.com/skills/image-pipeline-review) skill takes pasted upload code, storage scripts, loaders and deployment configuration and reports the traps covered here and a few neighbouring ones: photographs saved as PNG, files kept exactly as received, a redirect fallback that conceals the quota error, an unexplained list of widths, requests wider than the source, a binding left behind after transformations were switched off, lossy encoding on charts, lossy files encoded again, a preview address mistaken for a zone, overwritten originals, and generated variants that may not exist. Every finding names the place and gives a fix, and the answer closes with a single verdict. The store itself is at https://aiskills402.com.
