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.
https://cdn.steadylink.io/a/3f2a9c1e-8b4d-4e7a-a1c2-5d6e7f809a1b?w=1200&h=630&fit=cover&fm=webp&q=80Build a transformed link in the dashboard#
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#
| Parameter | Values | Default | Notes |
|---|---|---|---|
w (or width) | 1 to 8000 | none | Target width in pixels. |
h (or height) | 1 to 8000 | none | Target height in pixels. Give only one of w and h to keep the aspect ratio. |
fit | cover, contain, inside, outside | contain | How the image fits a w × h box. See Choose a fit. |
fm (or format) | webp, jpg (or jpeg), png | jpg | Output format. AVIF is not supported; fm=avif returns 400. |
q (or quality) | 30 to 95 | 82 | Output quality for webp and jpg. Ignored for png. |
focus | center, auto | center | auto picks the most detailed region of the image for a cover crop. |
fp-x, fp-y | 0 to 1 | none | An explicit focal point for a cover crop, as fractions of width and height. Send both. |
dpr | 0.1 to 3 | 1 | Scales 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. |
allowUpscale | true, false | false | Lifts the cap that stops a cover crop from enlarging the source more than 2×. |
v | revision number | current | Pins 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, ordpr. Without one of them the original is served unchanged, so?q=60on 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
400with a message naming the problem. The only other parameters a delivery URL accepts arev,token(for private files),preset, and the traffic-source tagsrefandutm_*. Do not append your own cache busters. - Aspect ratio. When you send both
wandh, 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=webpis the best default for photos and illustrations on the web: smaller than JPEG at the sameq.fm=jpgis the safest choice for email clients and older software.fm=pngis lossless and larger;qhas 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.
coverfills 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 orfocus=auto.contain,inside, andoutsidecurrently 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.
# 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=webpControl 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:
fp-xandfp-yin the URL. Explicit coordinates for this one link that say which part of the image the crop keeps:0,0keeps the top-left,1,1the bottom-right, and0.5,0.5the center. For a subject 42% from the left and 31% from the top, usefp-x=0.42&fp-y=0.31.focus=autoin 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.- A saved focal point on the file. Applied to every
coverrequest for that file that does not specify its ownfp-x/fp-yorfocus=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}'await steadylink.setFocalPoint(assetId, 0.42, 0.31);client.set_focal_point(asset_id, 0.42, 0.31)steadylink focal 3f2a9c1e-8b4d-4e7a-a1c2-5d6e7f809a1b --x 0.42 --y 0.31{ "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:
| URL | Cache lifetime | After a replacement |
|---|---|---|
Without v: ?w=800&fm=webp | 5 minutes, then refreshed in the background | Serves a variant of the new revision once the cached copy expires. |
With v: ?v=3&w=800&fm=webp | 1 year, immutable | Always 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=800and?w=800&fit=contain&q=82produce 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
429with codeasset_transform_cardinality_exceededorworkspace_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
coverURL, subject to the five-minute cache. - Clearing variants.
POST /api/assets/{asset_id}/purgedeletes 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 -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.
Build links in code#
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" });steadylink link 3f2a9c1e-8b4d-4e7a-a1c2-5d6e7f809a1b --w 1200 --h 630 --fit cover --fm webp --q 80The 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:
https://cdn.steadylink.io/a/3f2a9c1e-...?token=eyJhbGciOi...&w=800&fm=webpPrivate 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#
| Response | Likely cause |
|---|---|
400 Unsupported format | fm is not webp, jpg, jpeg, or png. |
400 Quality must be between 30 and 95 | q is out of range. |
400 Focal controls require fit=cover | focus=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 width | The same setting sent under both its short and long name. |
415 Transforms supported only for raster images | The file is not a JPEG, PNG, WebP, GIF, BMP, or TIFF. |
423 Scanning | The current revision is still being scanned. Retry shortly. |
429 with a *_transform_cardinality_exceeded code | The file or workspace reached its variant limit. |