Image URLs
Read an image doc and get ready-made src and srcset — plus the full anatomy and every transform parameter behind the /api/img URL.
Every optimized image on your site is one URL against one endpoint, /api/img — public by default,
and closable with options.access.serve.
You rarely write these URLs by hand. Every read of an image doc hands you a ready-made src and srcset, so the fastest integration is: read the doc, use the URL. Reading a single URL is also the fastest way to understand the whole pipeline.
The virtual URL fields
You don't need JavaScript to consume these URLs. Every read of an image doc — REST, GraphQL, Local API, and populated relationships — carries ready-made URLs as virtual fields, computed on read and never stored:
// GET /api/images/<id> (or a populated page.heroImage)
{
"id": "...", "alt": "...", "width": 2400, "height": 1600,
"src": "https://site.com/api/img/<id>?w=1280&fit=cover&q=90&fmt=auto&v=…",
"srcset": "https://site.com/api/img/<id>?w=400&h=267&fit=cover&q=90&fmt=auto&v=… 400w, …",
"placeholderURL": "https://site.com/api/img/<id>?w=32&h=21&fit=cover&q=40&fmt=auto&v=…",
"thumbnailURL": "https://site.com/api/img/<id>?w=160&h=160&fit=cover…"
}That's the whole integration story for a mobile app, a GraphQL client, or an RSS/OG generator: read the doc, use the URL. The URLs are absolute when serverURL is set, relative otherwise. They flow through relationship population, so a populated page.heroImage is render-ready with no second query.
By default a read gets natural-ratio URLs. Declare the render in the read's context to get src/srcset built for exactly that render:
// findByID / find / GraphQL — pass a render declaration in context
context: { image: { aspectRatio, quality, fit, format } }The srcset follows the project-wide pixelStep (default 50): one URL every 50px up to the source width (capped at maxDimension, 4096), with the top rung being the source's own width. An array pixelStep switches to a breakpoint ladder instead (e.g. next/image's [640, 750, 828, 1080, 1200, 1920, 2048, 3840]) — rungs below the source width plus the source width itself as the top rung; freeform requests still snap to the 50px grid.
The dense default means a wide original emits dozens of srcset URLs — several KB of string on
every read and population. Pass an array pixelStep (a fixed breakpoint ladder) if response size
matters more than fine-grained width selection.
The resolved config (slugs + options) is stashed on config.custom.payloadImages, so decoupled
tooling (an OG/sitemap generator, a CDN-purge script, a migration) can read it from just
payload, with no import.
URL anatomy
When you do build a URL by hand, here's the shape:
/api/img/64a1f2c…?w=1280&h=720&fit=cover&q=80&fmt=auto&v=1a2b3c
└ id └ size ─────┘ └ crop ─┘ └ encode ──┘ └ cache ┘/api/img/:id— the transform endpoint, mounted under Payload's API route. The id is a doc in your source collection (imagesby default).w=1280&h=720— the output box. Providing both pins the geometry; the endpoint focal-crops into it.fit=cover— how the source maps into the box.covercrops around the focal point.q=80&fmt=auto— encode settings.autonegotiates AVIF/WebP from the browser'sAccept.v=1a2b3c— a cache-bust token derived from the source's filename, filesize, and focal layers (focal point, plus hotspot/crop when set). The server ignores it; it exists so the URL changes when the image does (Caching).
The first request for a URL generates the variant with Sharp and streams it same-origin. The variant persists after the response. Every later identical request streams the stored copy.
GET /api/img/<id>?w=600&h=600&fit=cover # focal cover-crop, auto format
GET /api/img/<id>?w=1200&h=630&fit=cover&fmt=avif # an OG-sized AVIFParameters
| Param | Default | Meaning |
|---|---|---|
w / h | — | Target width / height in px. At least one is required. |
ar | — | Aspect ratio (16:9 or 1.78 — : is the only accepted separator). Derives the missing dimension. |
fit | cover | How the source maps into the box: cover (focal crop) · contain · inside · outside · fill. |
q | 90 | Quality, bucketed to multiples of 5 and clamped to qualityRange. |
fmt | auto | Encode format: auto (negotiate from Accept) · avif · webp · jpeg · png. |
v | — | Cache-busting token (derived from filename + filesize + focal layers). Ignored by the server. |
w and h snap to the pixelStep grid (default 50px) and clamp to maxDimension (default 4096). An array pixelStep's exact widths also pass the snap. q clamps to qualityRange (default 40–95). Output never upscales past the source.
Snapping and bucketing aren't cosmetic. They collapse the continuous parameter space into a finite set of possible variants. That's what makes the endpoint safe to expose (the DoS bounds) and the cache effective. See Reference for these options and their defaults.
The endpoint is public and unsigned by default. Generation is gated by the bounds above rather
than a shared secret. For fully untrusted traffic, raise pixelStep or lower maxDimension /
maxInputPixels and put a rate limiter or CDN in front —
Reference has every knob. To gate the endpoint itself,
set options.access.serve; the source collection's read access is enforced either way.
Format negotiation
With fmt=auto the endpoint picks the best format the browser's Accept header allows. WebP is the default modern target. AVIF only wins when transform.preferAvif is on, because AVIF encodes far slower and auto keeps the cold path fast. Explicit fmt=avif always works.
fmt=auto responses carry Vary: Accept so caches keep per-format copies straight. Explicit-format responses don't need it.
Rendering images
Fetch a render-ready image doc with imageFor and paint it with <ResponsiveImage> — srcset, placeholder, and focal crop included.
Image metadata
One upload-time decode gives every render a blur placeholder, a color palette, alpha flags, and a subject-aware crop — no extra files, no extra requests.