WebP and AVIF, straight from site.json

The built-in scaler now writes WebP and AVIF, encoder quality is yours to set per format in site.json, and changing either one takes effect on existing URLs without a manual cache clear.

Two ways to ask for a modern format

Docly scales images on two separate paths, and both can now produce WebP and AVIF.

From script, docly.scaleImage() takes the format as its last argument:

docly.scaleImage(bytes, 1200, 800, 0, 78, "webp");
//                     w     h  mode quality format

On a published URL the extension of the display filename decides. linkImage() does not scale anything itself - it builds the URL, and the scaling and encoding happen when that URL is served:

/photo.jpg/1024x1024x0/product.webp   -> WebP
/photo.jpg/1024x1024x0/product.avif   -> AVIF

Classic formats are encoded through GDI+, which is the fastest path available. WebP and AVIF go through Magick.NET, because GDI+ cannot write them at all.

One switch for the whole site

Passing the format on every call gets tedious. images.defaultFormat in #/site.json sets it once, for everything:

{
  "images": {
    "defaultFormat": "webp"
  }
}

An explicit format - in the call or in the URL - always wins over the default.

Quality is yours to set

Encoder quality is configuration now, and it takes three shapes:

"images": { "quality": 78 }                                     // one value for every format
"images": { "quality": { "jpeg": 90, "webp": 78, "avif": 50 } }  // per format
"images": { "quality": { "webp": 78, "default": 70 } }           // per format + fallback

Lookup runs per-format key -> scalar or default -> built-in default.

Format

Built-in default

JPEG

85

WebP

78

AVIF

50

PNG

Lossless - quality is ignored

An invalid value falls through to the next level and anything above 100 is clamped, so a typo in site.json cannot break image serving.

Quality can never be set from the URL. Same ownership rule as approved sizes: an arbitrary quality in the URL would mean one cache entry per size x quality - an unbounded cache, sized by whoever is doing the requesting rather than by you.

WebP and AVIF need a locked size policy

Check this first. If your site still has the default open size policy, a requested WebP or AVIF on the public URL path is served as JPEG or PNG instead - including when the request came from defaultFormat. Set images.sizePolicy to auto or strict to get the modern formats.

The reason is cost. WebP and AVIF encode through Magick.NET and are markedly more CPU-expensive than GDI+, and open keeps no whitelist of sizes - so the dimensions in the URL are whatever the caller asks for. Locking the policy puts a known set of sizes around the expensive encoder.

docly.scaleImage() is not affected. Its arguments come from your own server-side code, not from a request URL.

Policy

Behaviour

open (default)

Any size up to maxDimension. WebP/AVIF served as JPEG/PNG on the URL path.

auto

Accepts approved and discovered sizes; an unknown size snaps to the nearest one. The list grows from your own traffic.

strict

Same as auto, but an unknown size is rejected outright.

Changes take effect on their own

Scaled images are cached under a generation segment whose fingerprint is derived from images.quality, images.defaultFormat and images.sizePolicy. Change any of them and existing URLs point at a new, empty generation, so the image is re-encoded the next time someone asks for it.

Cosmetic edits deliberately do not trigger that. Casing, jpg versus jpeg, scalar versus object, key order, and numbers written as strings all normalise to the same fingerprint.

Roll this out outside peak hours. Regeneration is lazy and follows your traffic, so a quality or format change produces a CPU spike shaped like your traffic curve - and AVIF through Magick.NET is the most expensive thing in that spike.

What this means for existing sites

JPEG on the URL-scaling path now encodes at the same quality as everywhere else, 85. If you want a higher value on that path, set it explicitly:

"images": { "quality": { "jpeg": 90 } }

Check it worked

curl -sI "https://yoursite.com/photo.jpg/1024x1024x0/product.webp" | grep -i content-type
-> content-type: image/webp

If it comes back image/jpeg, your size policy is still open.