Skip to content
Talk to our solutions team

Image transforms

Derivatives are produced on the way out of an asset read, cached, and addressed by their canonical key. The pipeline is pure Go, so a deployment needs no system imaging library.

An asset URL accepts three vocabularies. The native grammar is exact; the other two exist so that a site migrating from Contentful or Strapi keeps its existing <img src> values.

Transforms are separated by /, a transform’s parameters by -, and every parameter carries a one-letter prefix.

?transform=resize-w_800-h_600/format-f_webp-q_80
TransformParametersEffect
resizew_, h_Preserve the aspect ratio. With both edges pinned, cover the box and crop the overflow
fillw_, h_Force the exact dimensions, abandoning the aspect ratio
cropw_, h_, g_A box-sized cutout at a gravity
formatf_, q_Re-encode, optionally at a quality

Gravities are centre, north, south, east, west.

An explicit transform= always wins over the translated vocabularies, so nothing that worked before they were accepted changed.

A resize is a bound. Asking for more than the source has returns the source. Images are never upscaled, which matches Contentful.

Append them to any asset URL.

/store/default/asset/id/01J9X…?w=800&fm=webp&q=80
IncomingTranslated toNote
?w=800resize-w_800-h_0Aspect-preserving. The commonest image URL there is
?w=800&h=600resize-w_800-h_600Covers the box and crops the overflow
?fit=scale&w=800&h=600fill-w_800-h_600The names cross over: Contentful’s scale is the native fill. Worth checking twice
?fit=crop&w=400&h=400crop-w_400-h_400-g_centre
?fit=crop&w=400&h=400&f=topcrop-w_400-h_400-g_northFocus becomes gravity
?fm=jpg&q=70format-f_jpeg-q_70Quality rides the encoder

Imaging is engaged by w, h, fit, fm or f. A bare ?q=80 is ignored, because q is too common a parameter name to read as imaging intent, and quality alone names no encoder.

jpg, jpeg, png, webp, gif, tiff, bmp.

An fm outside that list is refused by name before anything is read, rather than accepted and then failing further down.

f=Gravity
center, centrecentre
topnorth
bottomsouth
leftwest
righteast
top_left, top_rightnorth
bottom_left, bottom_rightsouth

The native gravity vocabulary has no corners, so a corner keeps its vertical half, which is the axis that usually carries the subject.

Strapi pre-generates four named sizes at upload. The same names resolve here, derived on demand, as a bound on the longest edge.

/store/default/asset/id/01J9X…?format=thumbnail
format=Bound
thumbnail245
small500
medium750
large1000

A named format is a complete request on its own and takes precedence over the Contentful parameters, since it carries its own dimensions.

Each of these returns 400 with a message naming the problem. The alternative would be an image that looks deliberate and is not, served with a 200, which the caller has no way to detect.

RequestResponse
fit=padRefused. Padding needs a background colour and a compositing step
fm=avif, fm=heif, fm=heicRefused. The encoder does not produce them
f=face, f=facesRefused. Use an explicit focus area
q= without fm=Refused. Quality belongs to an encoder, and no format was named
fit=crop or fit=thumb without both w and hRefused. A cutout has no aspect ratio to infer the missing edge from
fit=scale without both w and hRefused, for the same reason
An unknown fit or f valueRefused by name

A derivative is content-addressed by its canonical transform key, so two URLs that mean the same thing share one cached derivative. The first request for a given key produces it; later requests serve the stored bytes.

Set Cache-Control per store so a CDN in front of the service caches derivatives too. See Operations.

@kis.ai/clients/content builds the same URLs in JavaScript, so an <img src> can be composed on the client with no round-trip. It maps the same vocabulary onto the same native grammar, case for case.

  • Assets: uploads, ranged reads, presigned redirects
  • API: the asset routes and their parameters