Docly Developers
Knowledge base Pitfall By design

Restrict and register image sizes

In shortThe scaled image URL (/foto.jpg/1200x800x0/navn.jpg) is open by default: every unique size is a cache miss with heavy scaling and a new cache file on disk. A size policy locks scaling to a known set of sizes — and it is also what unlocks WebP/AVIF, which are downgraded to JPEG/PNG on the URL path until the policy is set to auto or strict. Encoder quality is a separate setting, images.quality, and can never be set from the URL.
Applies to Imagesimages.jsonsite.json
  1. What you'll see

    You just changed sizePolicy, or you are adopting WebP/AVIF, and something breaks. The symptoms:

    • Images suddenly return 400 after switching to strict — a size the page requests is not in allowedSizes.
    • An image comes back at a “nearly right” size under auto — it was snapped to the closest allowed size.
    • A size generated in client-side JS never shows up in “Scan now” — the scan only reads server-side source.
    • .webp/.avif is served as JPEG for as long as the policy is open.
    • Scaled images return 429 under load — the anti-DoS rate guard on scaling.
  2. What's actually happening

    The size key is WxHxM — the same string as the URL segment. WxH (no third part) is mode 0. There are three policy modes:

    ModeBehaviour
    open (default)Any size, only the maxDimension cap applies. .webp/.avif on the URL path is downgraded to JPEG/PNG.
    autoAccepts allowedSizes ∪ discoveredSizes. An unknown size snaps to the nearest allowed one; the list self-expands from traffic and scan.
    strictSame as auto, but an unknown size returns HTTP 400. No self-expansion.

    Two lists with different ownership: allowedSizes is yours (owner-maintained, never touched by scan or passive discovery), while discoveredSizes is the machine's (append-only, deduped, passive growth capped at 100).

    Snapping under auto only matches within the same scaling mode. If no allowed size exists with the requested mode, the request is rejected instead of snapped — that is the “nearly right size” symptom when the mode does not line up.

    Under open, a requested .webp/.avif on the URL path is downgraded to JPEG/PNG. This is a DoS defence (see Use AVIF and WebP image formats, #26952): open has no size whitelist, so the URL could otherwise request arbitrary sizes in the heavier formats. Server-authored docly.scaleImage and attachFile64 are not subject to the policy.

  3. What to do

    Roll the policy on in order — start permissive, capture the real sizes, then lock down.

    1. Open Folder Properties → the “Image sizes” tab (requires publish rights on the folder).
    2. Set sizePolicy to auto. This starts passive discovery and lets you scan.
    3. Run “Scan now” (a static dry-run — it writes nothing), review the diff, then Apply changes.
    4. Promote the sizes you want to keep from Discovered to Allowed.
    5. Add client-JS-generated and dynamically computed sizes to allowedSizes by hand — the scan never sees them. The same applies to any WxHxM URL you assembled by hand in a template: the scan finds literal linkImage calls, so a concatenated URL stays invisible and its size is refused once you switch to strict. Convert those to linkImage/linkFile before locking down — see Do not build file and image URLs by hand.
    6. Switch to strict and verify that no images return 400.

    The image settings live in #/images.json (in the folder's # system folder), shared per folder, keys read case-insensitively. The Image sizes tab and discovery write there — never to #/site.json and never to the app. An images section in #/site.json still works, but Docly moves it to #/images.json the first time it saves image settings. A site built on an app inherits the app's image settings key by key; a list such as allowedSizes in the site replaces the app's list as a whole (see Configure site settings and inherit them from the app).

    {
      "defaultFormat": "webp",
      "sizePolicy": "auto",
      "onDisallowed": "nearest",
      "maxDimension": 2000,
      "allowedSizes": ["1200x800x0", "600x400x0", "128x128x1"],
      "discoveredSizes": ["1024x1024x0", "300x300x0"]
    }
    images.*ValuesMeaning
    defaultFormatauto | jpeg | png | webp | avifDefault output format. The file extension in linkImage always overrides.
    sizePolicyopen (default) | auto | strictSee the mode table above.
    onDisallowednearest | rejectOverrides the default for an unknown size (auto→nearest, strict→reject).
    maxDimensionpx, default 2000Hard cap on W and H in all modes, including open. 0/unset ⇒ 2000.
    allowedSizes["WxHxM", …]Owner-maintained. Never touched by scan or passive discovery. No cap.
    discoveredSizes["WxHxM", …]Machine-maintained. Append-only + dedupe. Passive growth capped at 100.
    quality1–100, or per format: { "jpeg": 90, "webp": 78, "avif": 50, "default": 70 }Encoder quality for scaled images. Per-format key first, then the number or default, then the built-in default. PNG is lossless and ignores it. Never settable from the URL.

    Watch out: strict with an empty allowedSizes rejects every scaled image with 400 (warning strict_no_sizes). Promote at least one real size before you switch.

    The full M-mode table (-1…5), the snapping algorithm and every linkImage overload live in the reference pages — see linkImage, scaleImage and the site.json reference.

Last reviewed 17 Oct 2026