Skip to content

Image transformations

Resize, crop, and convert images by adding query parameters to their stable link, with focal points for crops and cached variants that follow every replacement.

On this page

Use this guide when you want one uploaded image to serve many sizes: a 1200-pixel hero, a 400-pixel card thumbnail, and a 1200×630 social preview, all from the same stable link. You add query parameters to the link, SteadyLink generates the variant on first request, and the stored original is never modified. Because the parameters ride on the stable link, every variant follows the file when you replace it.

Text
https://cdn.steadylink.io/a/3f2a9c1e-8b4d-4e7a-a1c2-5d6e7f809a1b?w=1200&h=630&fit=cover&fm=webp&q=80

Open an image, go to the Transform tab, and set Width, Height, Format, and Quality. Choose Copy link to copy the transformed URL. The image is processed when the link is first opened, not when you copy it. For other file types the tab shows that transforms are not available; use File conversions to turn a document or video into another format instead.

Parameters#

ParameterValuesDefaultNotes
w (or width)1 to 8000noneTarget width in pixels.
h (or height)1 to 8000noneTarget height in pixels. Give only one of w and h to keep the aspect ratio.
fitcover, contain, inside, outsidecontainHow the image fits a w × h box. See Choose a fit.
fm (or format)webp, jpg (or jpeg), pngjpgOutput format. AVIF is not supported; fm=avif returns 400.
q (or quality)30 to 9582Output quality for webp and jpg. Ignored for png.
focuscenter, autocenterauto picks the most detailed region of the image for a cover crop.
fp-x, fp-y0 to 1noneAn explicit focal point for a cover crop, as fractions of width and height. Send both.
dpr0.1 to 31Scales the original's dimensions when neither w nor h is set. Has no effect when w or h is set, so multiply the width yourself for high-density screens.
allowUpscaletrue, falsefalseLifts the cap that stops a cover crop from enlarging the source more than 2×.
vrevision numbercurrentPins the variant to one revision. See Caching and replacements.

Some rules apply to every request:

  • Transforms run only for raster images: JPEG, PNG, WebP, GIF, BMP, and TIFF. Asking for a size or format on an SVG, PDF, video, or other file returns 415. The original is still available without parameters.
  • A transform needs w, h, fm, or dpr. Without one of them the original is served unchanged, so ?q=60 on its own does nothing.
  • Strict validation. An out-of-range value, an unknown parameter, the same parameter twice, or more than 8 transform parameters returns 400 with a message naming the problem. The only other parameters a delivery URL accepts are v, token (for private files), preset, and the traffic-source tags ref and utm_*. Do not append your own cache busters.
  • Aspect ratio. When you send both w and h, the ratio must stay between 1:50 and 50:1.

Choose a format#

If you resize without fm, the output is JPEG, whatever the source format. Always send fm when the source has transparency or when you want WebP.

  • fm=webp is the best default for photos and illustrations on the web: smaller than JPEG at the same q.
  • fm=jpg is the safest choice for email clients and older software.
  • fm=png is lossless and larger; q has no effect.

PNG and WebP images with a full alpha channel are converted to RGB when transformed, so their transparency is lost even with fm=png. An animated GIF becomes a single frame; resize GIFs with fm=png or fm=webp rather than the JPEG default. For logos and icons that rely on transparency, serve the original without parameters, or check the transformed result before you publish it.

Choose a fit#

fit only matters when you send both w and h.

  • cover fills the box exactly, cropping whatever does not fit. Use it for thumbnails, cards, and social images that must be an exact size. The crop is centered unless you set a focal point or focus=auto.
  • contain, inside, and outside currently behave the same way: the image is scaled down to fit inside the box, keeping its aspect ratio and without cropping. The result can be smaller than the box in one dimension. These modes never enlarge an image.
Text
# Exactly 400 × 400, cropped around the most detailed area
https://cdn.steadylink.io/a/3f2a9c1e-...?w=400&h=400&fit=cover&focus=auto&fm=webp

# At most 1600 × 900, aspect ratio kept, never cropped
https://cdn.steadylink.io/a/3f2a9c1e-...?w=1600&h=900&fm=webp

Control the crop with focal points#

A cover crop that cuts off someone's face is the most common problem with automatic resizing. There are three ways to steer it, in order of precedence:

  1. fp-x and fp-y in the URL. Explicit coordinates for this one link that say which part of the image the crop keeps: 0,0 keeps the top-left, 1,1 the bottom-right, and 0.5,0.5 the center. For a subject 42% from the left and 31% from the top, use fp-x=0.42&fp-y=0.31.
  2. focus=auto in the URL. SteadyLink finds the most detailed region of the image and positions the crop toward it. It is deterministic: the same image always produces the same crop.
  3. A saved focal point on the file. Applied to every cover request for that file that does not specify its own fp-x/fp-y or focus=auto.

You cannot combine focus=auto with fp-x/fp-y, and both require fit=cover; otherwise the request returns 400.

Save a focal point#

A saved focal point lets every existing cover link crop correctly without editing any URL. Set it with the API, the SDKs, or the CLI:

curl -X PUT "https://api.steadylink.io/api/platform/assets/$ASSET_ID/focal-point" \
  -H "X-API-Key: $STEADYLINK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"x": 0.42, "y": 0.31}'
200 OKResponse
{ "x": 0.42, "y": 0.31 }

Coordinates are stored to four decimal places. Clear the saved point with DELETE /api/platform/assets/{asset_id}/focal-point (204 No Content). Because the route is under /api/platform, an API key needs the workspace:admin scope to change it; a signed-in member needs permission to change files.

The focal point belongs to the file, not to a revision, so it survives replacements. If a new version has a different composition, update the focal point when you replace it.

Caching and replacements#

Each variant is generated once per revision and stored, so the first request for a new combination of parameters is slower than the ones after it. On top of that, delivery responses are cached by browsers and edges by URL:

URLCache lifetimeAfter a replacement
Without v: ?w=800&fm=webp5 minutes, then refreshed in the backgroundServes a variant of the new revision once the cached copy expires.
With v: ?v=3&w=800&fm=webp1 year, immutableAlways serves revision 3.

Use links without v anywhere the image should follow replacements, which is almost always. Use v when a page must keep showing exactly one version, for example an archived press release, and accept that it never updates.

Some details that matter at scale:

  • Equivalent URLs share one variant. Defaults are filled in before a variant is looked up, so ?w=800 and ?w=800&fit=contain&q=82 produce the same stored image. They are still different URLs for browser and edge caches.
  • Variant limits. To stop runaway URL generation, SteadyLink caps how many distinct variants one file and one workspace can create (by default 100 per file and 10,000 per workspace). A request for a new variant beyond the cap returns 429 with code asset_transform_cardinality_exceeded or workspace_transform_cardinality_exceeded; variants that already exist keep working. Generate a small, fixed set of sizes instead of arbitrary widths.
  • Changing a focal point produces new variants on the next request for each cover URL, subject to the five-minute cache.
  • Clearing variants. POST /api/assets/{asset_id}/purge deletes the stored variants of every revision of a file; they are regenerated on demand. Deleting a revision deletes its variants.

Named presets#

A preset stores a set of parameters under a slug so links stay short and you can change a size in one place. Create one with the API; workspace admins (or an API key with assets:write) can manage presets.

curl
curl -X POST https://api.steadylink.io/api/usage/transform-presets \
  -H "X-API-Key: $STEADYLINK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Card thumbnail", "slug": "card", "params": {"w": 400, "h": 300, "fit": "cover", "fm": "webp"}, "isPublic": true}'

Then request https://cdn.steadylink.io/a/{asset_id}?preset=card. Only presets created with isPublic: true can be used in delivery URLs; any other slug returns 404. A preset cannot be combined with other transform parameters (only with v), and the slug must be lowercase letters, digits, and hyphens. Leave fp-x/fp-y out of presets: once the defaults are filled in, a preset with focal coordinates can exceed the 8-parameter limit and fail at delivery. Use focus=auto or the file's saved focal point instead.

The SDK and CLI build the same URLs for you, so you never concatenate query strings by hand.

import { assetUrl } from "@steadylink/sdk";

steadylink.link(assetId, { width: 1200, height: 630, fit: "cover", focus: "auto", format: "webp", quality: 80 });
steadylink.link(assetId, { width: 400, height: 400, fit: "cover", focalPoint: { x: 0.42, y: 0.31 } });

// Without a client, for example in browser code
assetUrl(assetId, { width: 800, format: "webp" });

The SDK clamps quality to 30–95 and focal coordinates to 0–1 before building the URL. For next/image, use the loader described in Images in Next.js.

Private images#

Transforms work on private files too. Add the signed link's token alongside the transform parameters:

Text
https://cdn.steadylink.io/a/3f2a9c1e-...?token=eyJhbGciOi...&w=800&fm=webp

Private responses, transformed or not, are sent with Cache-Control: private, no-store, so they are never stored by shared caches and every request reaches SteadyLink. See Private links.

Troubleshooting#

ResponseLikely cause
400 Unsupported formatfm is not webp, jpg, jpeg, or png.
400 Quality must be between 30 and 95q is out of range.
400 Focal controls require fit=coverfocus=auto or fp-x/fp-y without fit=cover.
400 Unknown params: ...A parameter SteadyLink does not recognize, often a cache buster or a typo.
400 Conflicting parameters: w and widthThe same setting sent under both its short and long name.
415 Transforms supported only for raster imagesThe file is not a JPEG, PNG, WebP, GIF, BMP, or TIFF.
423 ScanningThe current revision is still being scanned. Retry shortly.
429 with a *_transform_cardinality_exceeded codeThe file or workspace reached its variant limit.

Next steps#