Images
Resize, crop, convert and blur any image in your media library by adding parameters to its URL.
Every file in your media library has a public URL on the CDN. Add parameters to an image's URL and Capa returns it resized, cropped or converted. Each variant is cached at the edge for a year.
https://cdn.capacms.com/files/<file>?width=800&format=webpThere is nothing to export and nothing to configure.
Get the URL
A media field comes back from the API with the file's url:
"cover": { "id": "…9b", "url": "https://cdn.capacms.com/files/winter-field-guide-cover_1759302000000_3f9c1a7b2e4d.jpg",
"alt": "A lit tent below snowy peaks at dawn", "type": "image",
"width": 3200, "height": 2133 }width and height are the original's pixel size. alt is the field's alt text, or the file's own when the field has none.
You can also copy a file's URL from the media library in the admin. See Media.
Parameters
| Parameter | Values | Default | What it does |
|---|---|---|---|
width | 1 to 4096 | none | Target width in pixels, before dpr. |
height | 1 to 4096 | none | Target height in pixels, before dpr. |
fit | inside, cover, contain | inside | inside keeps the aspect ratio within the box. cover fills the box and crops. contain fills it and pads, transparent or white on JPEG. |
dpr | 1 to 3 | 1 | Multiplies width and height. width=200&dpr=2 gives 400 pixels. |
upscale | 0, 1 | 0 | An image is never enlarged past its source size unless you send 1. |
format | webp, avif, jpeg, png, auto | the source format | The output format. jpg works as a spelling of jpeg. |
quality | 1 to 100 | 80 | Encoder quality for lossy formats. Ignored for PNG. |
blur | 1 to 256 | none | A tiny blurred placeholder: a square resize to that many pixels, then a blur. |
keepMetadata | 0, 1 | 0 | On a transformed image, camera metadata and colour profiles are removed unless you send 1. |
Only width, height, blur, format and quality start a transform. fit, dpr, upscale and keepMetadata change how one runs, and do nothing on their own.
A value out of range is a 400 with a JSON body that names the parameter. It is never ignored, and never a 500.
Parameters Capa does not know pass through untouched, so cache busters such as ?v=3 and campaign tags keep working.
Common recipes
A 400-pixel-wide version, aspect ratio kept:
/files/winter-field-guide-cover_1759302000000_3f9c1a7b2e4d.jpg?width=400A 400 by 300 crop for a card:
/files/winter-field-guide-cover_1759302000000_3f9c1a7b2e4d.jpg?fit=cover&height=300&width=400Sharp on high-density screens, 800 pixels in a 400-pixel box:
/files/winter-field-guide-cover_1759302000000_3f9c1a7b2e4d.jpg?dpr=2&width=400WebP at quality 60:
/files/winter-field-guide-cover_1759302000000_3f9c1a7b2e4d.jpg?format=webp&quality=60&width=800A 24-pixel blurred placeholder to show while the real image loads:
/files/winter-field-guide-cover_1759302000000_3f9c1a7b2e4d.jpg?blur=24Responsive images
Let the browser pick a width:
<img
src="https://cdn.capacms.com/files/winter-field-guide-cover_1759302000000_3f9c1a7b2e4d.jpg?format=webp&width=800"
srcset="
https://cdn.capacms.com/files/winter-field-guide-cover_1759302000000_3f9c1a7b2e4d.jpg?format=webp&width=400 400w,
https://cdn.capacms.com/files/winter-field-guide-cover_1759302000000_3f9c1a7b2e4d.jpg?format=webp&width=800 800w,
https://cdn.capacms.com/files/winter-field-guide-cover_1759302000000_3f9c1a7b2e4d.jpg?format=webp&width=1600 1600w"
sizes="(max-width: 600px) 100vw, 600px"
alt="A lit tent below snowy peaks at dawn"
width="800" height="533">To offer AVIF with a fallback, use <picture> and one <source> per format.
Use it with next/image
Point next/image at Capa with a custom loader. Next then requests the sizes it needs straight from the CDN.
// capa-image-loader.ts
export default function capaImageLoader({
src,
width,
quality,
}: {
src: string;
width: number;
quality?: number;
}) {
const url = new URL(src);
url.searchParams.set("format", "webp");
if (quality && quality !== 80) url.searchParams.set("quality", String(quality));
url.searchParams.set("width", String(width));
return url.toString();
}// next.config.ts
import type { NextConfig } from "next";
const config: NextConfig = {
images: { loader: "custom", loaderFile: "./capa-image-loader.ts" },
};
export default config;import Image from "next/image";
<Image src={cover.url} alt={cover.alt ?? ""} width={cover.width} height={cover.height} sizes="100vw" />;The loader writes its parameters in alphabetical order and leaves out the default quality. That is the spelling Capa caches, so no request is redirected (see below).
Choose the format yourself
format=auto picks AVIF or WebP from the browser's Accept header. Through the CDN today it answers in the source format, so name the format you want, or offer several with <picture>.
PNG is lossless. Asking for it on a photograph makes the file far larger, so it is never chosen for you.
One spelling per image
?width=200&format=webp and ?format=webp&width=200 are the same image. Capa answers the canonical spelling and redirects every other one to it with a cacheable 301, so each variant is one object at the edge.
The canonical spelling sorts the parameters alphabetically, leaves out any parameter set to its default, and normalises values (format=JPG becomes format=jpeg). Write URLs that way and you skip the redirect.
A URL that uses only width, height and blur is never redirected, however it is written.
Rotation and metadata
When a transform runs, the photo is first turned the right way up from its camera orientation, so width=400 means 400 pixels of the picture as a person sees it.
Camera metadata, location included, is then removed. Send keepMetadata=1 to keep it and the colour profile.
A URL with no transform returns the file exactly as it was uploaded, metadata included. If a photo may carry a location, serve it with at least a width or a format.
SVG
An SVG with no parameters is served as the SVG document itself, with anything that could run script removed first.
Any size or format parameter renders it to a raster image instead, PNG unless you ask for another format. With only a format and no size, the long edge is 1024 pixels. An SVG with no viewBox and no absolute size cannot be measured, so a resize answers 400.
What is passed through untouched
Some files come back as stored, with an X-Capa-Transform header that says why:
X-Capa-Transform | When |
|---|---|
skipped-size | The source is larger than 25 MB. |
skipped-unsupported | An image type Capa cannot decode, such as BMP. |
skipped-non-image | Not an image: a PDF, a video, a document. |
skipped-undecodable | The file claims to be an image and could not be read. |
sanitised-svg | An SVG that had something removed. |
An animated GIF or WebP passes through untouched with no parameters. With a resize or format, only the first frame is used.
Limits and errors
The longest edge of the output is capped at 4096 pixels, measured after dpr. width=2000&dpr=3 is a 400, not a smaller image.
| Status | When |
|---|---|
400 | A parameter value is out of range. The body names the parameter. |
404 | No such file. Never cached. |
503 | The transform took too long. Retry after Retry-After. |
Caching
Each variant is cached for a year, at the edge and in the browser. The ETag changes when the file or the parameters change, and only then.
Replacing a file in the media library keeps its URL, but it does not purge the edge. The edge and any browser that already holds the old copy can keep showing it for up to a year. When a visitor must see the new image, upload it as a new file instead, or add a cache buster such as ?v=2.
Deleting a file removes every variant from the edge.