Guides
Transforms
Named image sizes. Your code says avatar and never a width, a format, or a provider's URL syntax.
Define once#
In the dashboard under Transforms, or in config your agent can apply. Each one sets a width and height (either may be blank to scale by the other), a fit, an optional quality and an optional format.
transforms:
avatar: { width: 256, height: 256, fit: cover, format: auto }
thumb: { width: 400, fit: scale_down, format: webp, quality: 80 }
hero: { width: 1600, height: 600, fit: cover, format: auto }fit is one of cover, contain, scale_down, crop, pad. format is
auto, webp, avif, jpeg or png; auto serves AVIF or WebP when the
browser accepts it.
Use everywhere#
user.avatar_transform_url("thumb")$user->avatarTransformUrl('thumb');file.transforms.thumb
await hutch.transformUrl(id, { transform: "thumb" })# Public: the stable URL, which redirects to the crop
curl -L https://filehutch.com/f/file_abc123/thumb -o thumb.webp
# Private: a signed transform URL that expires
curl -X POST https://filehutch.com/api/v1/files/file_abc123/transform_url \
-H "Authorization: Bearer $FILE_HUTCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "transform": "thumb", "expires_in": 3600 }'Public images carry their transform URLs on the file payload, so reading one costs no extra request. Private images get a signed source and need a call, because the URL has to expire.
Change the size later and every app asking for that name gets the new crop. No deploy, no cache-busting query string.
Where they render#
Rendering belongs to the storage a project uses, not to FileHutch. Managed storage renders them. So does your own R2 bucket behind a Cloudflare zone with image transformations enabled — set that zone as the connection's transform base URL.
An S3 or B2 bucket stores and serves your files perfectly well but
cannot transform them. Ask for one and you get transforms_unsupported with a
message naming what to set up — never a URL that would 404 at the CDN.
The monthly allowance#
Every plan includes a number of transforms a month on managed storage: 1,000 on Free, 5,000 on Small, 20,000 on Pro and 50,000 on Studio. A transform is one crop of one image, counted once a period however often it loads, so an avatar shown ten thousand times is one. On your own R2 your Cloudflare zone renders and bills them, and they are not counted here.
Past the allowance, a new crop through /f/file_…/avatar
redirects to the original image rather than failing, and
POST /api/v1/files/file_…/transform_url answers 402 plan_limit. Crops
already made this period keep working. With overages on (Settings → Plan) new
crops are billed at $1 per 1,000, inside the same spending cap as storage.