# Images

Source: https://capacms.com/docs/guides/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=webp
```

There is nothing to export and nothing to configure.

## Get the URL

A media field comes back from the API with the file's `url`:

```json
"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](https://capacms.com/docs/editor/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=400
```

A 400 by 300 crop for a card:

```
/files/winter-field-guide-cover_1759302000000_3f9c1a7b2e4d.jpg?fit=cover&height=300&width=400
```

Sharp on high-density screens, 800 pixels in a 400-pixel box:

```
/files/winter-field-guide-cover_1759302000000_3f9c1a7b2e4d.jpg?dpr=2&width=400
```

WebP at quality 60:

```
/files/winter-field-guide-cover_1759302000000_3f9c1a7b2e4d.jpg?format=webp&quality=60&width=800
```

A 24-pixel blurred placeholder to show while the real image loads:

```
/files/winter-field-guide-cover_1759302000000_3f9c1a7b2e4d.jpg?blur=24
```

## Responsive images

Let the browser pick a width:

```html
<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.

```ts
// 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();
}
```

```ts
// next.config.ts
import type { NextConfig } from "next";

const config: NextConfig = {
  images: { loader: "custom", loaderFile: "./capa-image-loader.ts" },
};

export default config;
```

```tsx
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.

## Related

* [Media](https://capacms.com/docs/editor/media) for uploading files and writing alt text.
* [Caching](https://capacms.com/docs/concepts/caching) for how purges work.
