Scale and convert images in Docly
-
What you'll see
A developer — or an AI coding agent — needs a smaller or reformatted copy of an image that already sits in the site's asset folder, and concludes that Docly cannot do it. The usual path to that wrong conclusion:
- Query parameters are tried first:
/img/photo.webp?w=1200,?width=1200. They return the original, unscaled, at 200 — so they look like they silently do nothing. - The documented URL form is tried next:
/img/photo.jpg/1200x675x0/photo.webp. It returns 404.
The conclusion drawn is "no image tooling available, re-export the file by hand". Both observations are real; the conclusion is wrong.
- Query parameters are tried first:
-
What's actually happening
Every uploaded file in Docly is a document, not a loose file on disk. A JPEG dropped in an asset folder is stored with schema
Uploaded Image, and the actual bytes live as an embedded file inside that document. The public path (/img/photo.jpg) resolves to the document; the bytes are addressed by an embedded id.That is why the scaling URL needs four segments, not three:
/<public path>/<embeddedId>/<WxHxM>/<display name>.<format>Leaving out the embeddedId does not produce a helpful error. The router simply finds no route and answers 404 — indistinguishable from "this feature does not exist". That single missing segment is what sends people down the wrong path.
Reading the status code
404 The URL shape is wrong — almost always the missing embeddedId segment. 400 The shape is right. The size is rejected by images.sizePolicy. Underautowith bothallowedSizesanddiscoveredSizesempty there is nothing to snap to, so a size is refused until a rendered page has used it (rendered pages add their sizes todiscoveredSizes).200 Working. The extension on the display name decided the output format. Let linkImage build the URL
linkImage()builds exactly this URL for you, and it is available in every server-side context: display templates,.hashpages, master pages and API scripts. For an image that is not on the document being rendered, pass the file object fromdocly.getFile()ordocly.getFiles()as the first argument. The URL only has to be assembled by hand where no server-side code runs — a static CSS file, client-side JavaScript, or a reference from outside the site — and then the embeddedId has to be looked up first.Scaling up costs more than it saves
The URL has no quality parameter. The encoder quality comes from
qualityin#/images.json— one number for every format, or per format such as{ "jpeg": 90, "webp": 78, "avif": 50, "default": 70 }— and from a built-in default when it is not set. PNG is lossless and ignores it. Requesting a size larger than needed re-encodes from the full-resolution source and can produce a file heavier than the original. Measured on a 199 kB JPEG whose existing WebP was 155 kB:1600x900x0→ 246 kB,1100x620x0→ 151 kB,800x450x0→ 96 kB. Always verify the byte count of the derivative instead of assuming a smaller box means a smaller file. -
What to do
1. Find the embeddedId with a probe file
Write a throwaway
.hashthat dumps the file object. This is the fastest way to see what you are actually working with, and it costs nothing to delete afterwards.#setmime("text/plain")# #{ let fi = docly.getFile("#/Root/img/photo.jpg"); write(JSON.stringify(fi)); }#Note the path form: filesystem functions take a single
#/, not the##/used by#include()in bundle files. The response contains:{ "File": "7ba189d5-b124-4eb6-b30c-82a817adb909.jpg", <-- embeddedId "Title": "photo.jpg", "filepath": "/#/Root/img/photo.jpg", "fileschema": "Uploaded Image", "Filesize": 199170 }Fileis the embeddedId. Delete the probe when you are done — it is served publicly like any other page.2. Register the sizes you intend to use
In
#/images.json(or theimagessection of#/site.json). Withautoand empty lists a scaled request returns 400 until a rendered page has used that size — declare the sizes you use:{ "sizePolicy": "auto", "defaultFormat": "webp", "onDisallowed": "nearest", "maxDimension": 2000, "allowedSizes": ["1600x900x0", "1100x620x0", "800x450x0"], "discoveredSizes": [] }WebP and AVIF also require
sizePolicyto beautoorstrict— under the defaultopena.webpdisplay name is served as JPEG regardless of the extension.3. Assemble the URL
/img/photo.jpg/7ba189d5-b124-4eb6-b30c-82a817adb909.jpg/800x450x0/photo.webp \_________/ \_________________________________________/ \________/ \_______/ public path embeddedId WxHxM name.formatUsable anywhere a URL is: a CSS
background-image, a static<img src>, an Open Graph tag. The extension on the last segment picks the format —.jpg,.png,.webp,.avif..svgand.gifare never converted.In template code, do not write this URL out. The breakdown above is here so you can read and debug a scaled URL — not as a construction recipe. From
#JSusedocly.linkImage(Image1, 800, 450, 0, 'photo.webp'), ordocly.linkImage(f, f.Image1, …)when the image lives on another document;docly.linkFiletakes the same two forms for non-image files. Assembling the path by hand skips the encoding the functions do and hides the size from the Image sizes → Scan, so it will not be registered inallowedSizes. See Do not build file and image URLs by hand. Hand-assembly is for the places a function cannot run — client-side JavaScript and references from outside the site.4. Measure the result
Request the derivative and compare bytes against the original before committing to it. If the derivative is heavier, pick a smaller box or lower
qualityin#/images.json— the URL itself has no quality parameter.When to reach for scaleImage instead
docly.scaleImage(bytes, w, h, mode, quality, format)runs server-side, takes and returns bytes, exposes quality, and is not subject to the size policy. Use it when you need a one-off derivative with its own quality, outside the size policy.scaleImageis available in pages, templates and API scripts; writing the result to a file withdocly.saveFile()needs an API script, becausesaveFileexists only there.Rule of thumb
Docly scales and converts images on serving. The URL is filepath / embeddedId / WxHxM / name.format — and the embeddedId is the
Fileproperty fromdocly.getFile(). A 404 means you left it out; a 400 means the size is not registered inimages.allowedSizes. Never conclude from a 404 that scaling is unavailable.
Last reviewed 17 Oct 2026