The problem with resizing at upload time
The common first approach is to resize when the file is uploaded: generate a thumbnail, a medium and a large, and store all three. It works until a designer asks for a 300 pixel version, or the layout changes and nothing matches any more. Now you have a backfill job to write.
Resizing at request time avoids that. You keep the original and describe the version you want in the URL. The first request produces it, and your layout is free to change without touching stored files.
How the URL works
An image is served from /images/get/ followed by its ID. Everything after ?transform= is a pipeline of steps, applied in order and separated by semicolons.
https://api.fileboy.io/images/get/FILE_ID?transform=resize:400x300resize:400x300 is width by height. If you only care about one dimension, use _ for the other and the aspect ratio is kept.
# 400 wide, height follows the aspect ratio
?transform=resize:400x_
# 300 high, width follows
?transform=resize:_x300Choose how the image fits the box
When both dimensions are given and the shapes differ, fit decides what happens. Add options after the size with ~.
cover: fill the whole box and crop the overflow. Good for avatars and cards.contain: show the whole image and pad the rest (set a colour with~background:hex(fff), using a 3, 6 or 8 digit hex value).fill: stretch to the exact size.insideandoutside: keep the aspect ratio and stay within, or at least reach, the size.
?transform=resize:200x200~fit:cover
?transform=resize:200x200~fit:contain~background:hex(fff)Chain steps together
Steps run left to right, so you can resize, then convert, then set quality in one URL. Format and quality can each appear once per URL.
?transform=resize:800x_;format:webp;quality:80Quality applies to JPEG, WebP and AVIF output, on the same scale. PNG and TIFF ignore it. Every transform in the image transform docs works, and any transform the image service cannot apply is answered with a 400 that says why.
Build the URL from code
Hand-writing query strings gets old quickly. The client libraries build the URL from an object, so you get type checking and do not have to remember the separators.
import { Image } from "@cd2/fileboy-react"
<Image
id={photoId}
transforms={[
{ type: "resize", width: 800 },
{ type: "format", format: "webp" },
{ type: "quality", value: 80 },
]}
/>From a server, the Node, Ruby, Python and PHP libraries build the same URL. Set the client up as shown in the server upload guide.
const url = fileboy.buildImageUrl(photoId, {
transforms: [
{ type: "resize", width: 800 },
{ type: "format", format: "webp" },
{ type: "quality", value: 80 },
],
})url = fileboy.build_image_url(photo_id, transforms: [
{ type: :resize, width: 800 },
{ type: :format, format: :webp },
{ type: :quality, value: 80 }
])url = client.build_image_url(photo_id, transforms=[
{"type": "resize", "width": 800},
{"type": "format", "format": "webp"},
{"type": "quality", "value": 80},
])$url = $fileboy->buildImageUrl($photoId, transforms: [
['type' => 'resize', 'width' => 800],
['type' => 'format', 'format' => 'webp'],
['type' => 'quality', 'value' => 80],
]);curl "https://api.fileboy.io/images/get/FILE_ID?transform=resize:800x_;format:webp;quality:80" -o photo.webpImage transforms are unlimited on every Fileboy plan, so resizing more variants does not change your bill. Files, video, storage and transfer are what the plans limit. See pricing.
Before you rely on it
- Pick from a small set of sizes. Arbitrary widths work, but a handful of fixed ones (say 320, 640, 960, 1280) means each is generated once and reused.
- Let Fileboy pick the format.
format:autoreads the browser'sAcceptheader and serves AVIF, then WebP, then the original format, so a plainimggets the smallest file it can show. Useformat:avif,format:webporformat:jpegto fix one. - Originals are never changed. Asking for a smaller version leaves the stored file as it was uploaded.
Checked against the current libraries in November 2026. Found something that no longer works? Tell us.