Skip to content

Output formats

Colophon writes PNG unless you say otherwise. WebP, JPEG and AVIF are each one setting:

export default defineConfig({
format: "webp", // "png" (the default), "jpeg", "webp" or "avif"
quality: 80, // the default; 1 to 100, ignored by png
});

The rasteriser still draws the picture, and the finished raster is encoded into the format you asked for. So this works with a custom rasteriser as well as with the default one.

The two-post site the numbers below come from, at four sizes each, with the default quality:

Format Whole build A 1200x630 landscape
png 396KB 82KB
jpeg 188KB 30KB
webp 112KB 18KB
avif 92KB 16KB

WebP is the usual choice. It is read by current browsers and by the crawlers these images are for, it comes out at about a quarter of the size of the PNG, and it is no slower to write, because a lossy encoding is cheaper than the level-9 zlib pass a PNG gets. AVIF is smaller again but slower to encode, and most of the platforms these images are made for will not display it. See What the platforms read.

The pictures are the same to look at. At 80 the gradients these templates are mostly made of hold up; below about 50 they start to band.

JPEG has no transparency, so a template that left any gets it flattened onto black. Every built-in template paints its background edge to edge, so this only comes up in a custom one.

The ordering in that table is the usual one rather than a fixed one. PNG loses to JPEG on photographs and on long gradients, and can win on flat colour, because a run of identical pixels is what PNG compresses best and what JPEG spends bits on. Two samples from the gallery in this repository show it: card-wide-solid is 19KB as a PNG against 24KB as a JPEG, and theme-slate is 7KB against 6KB, near enough the same file either way.

Across all 30 samples, at the default quality of 80:

Format The sample gallery
png 2300KB
jpeg 1002KB
webp 539KB
avif 496KB

So JPEG is the smaller by a wide margin over a mixed set of images, most of which have a gradient or a mesh in them. A site whose template is a flat background with a line of text on it is the case where that gap closes, and where the PNG can be the smaller file.

Browsers read all four of these formats. The crawlers behind link previews are a different set of programs, and they are some way behind.

AVIF is not a safe choice for a share image today. Testing by Joost de Valk, published in December 2024, covered eleven platforms. AVIF rendered on Facebook, Pinterest, Threads and WhatsApp. It did not render on Bluesky, Discord, iMessage, LinkedIn, Mastodon, Slack or X. Separate testing by Darek Kay, last updated in November 2025, found only Facebook rendering AVIF at all, with WhatsApp showing it in the wrong colours. A platform that cannot read the file posts the link with no image on it, which is the outcome the image is there to prevent.

Both dates are given so you can judge how far those results have aged. Support does move, and if you are reading this a long way after them it is worth checking rather than assuming the position still holds.

WebP came out of both sets of tests working: all eleven platforms in the first, and every platform except Xing in the second. That is further than the documentation goes. Facebook’s og:image reference still asks for image/jpeg, image/gif or image/png and says nothing about WebP, and it is not alone in that. So WebP here is a choice made on tested behaviour rather than on a documented guarantee. PNG and JPEG are the two formats every platform both documents and reads, which is the conservative answer if you would rather not rest on someone else’s testing.

An image is named after the format it holds, so a build writing WebP writes my-post-og.webp. jpeg is written .jpg, which is what the web settled on.

Changing the format therefore renames every image, and the files already written are left where they are. Nothing here knows whether something is still serving them, and deleting a URL somebody has shared is not a decision to make on a project’s behalf. Clear the output directory yourself if you want them gone.

A custom placement names its own files and is not told the format, so it is the one place the extension is yours to keep in step.

Some platforms have a ceiling of their own, such as X, which refuses an upload over 5MB. maxBytes records that ceiling:

export default defineConfig({
format: "webp",
quality: 90,
maxBytes: 5_000_000,
});

An image over the cap is encoded again ten quality points lower, and again, down to a floor of 30. Stepping rather than searching for the best quality that fits is deliberate: each step is a whole encoding, and a search would cost several more of them for a difference nobody can see.

An image that will not fit even at 30 is written anyway and reported through onWarning:

colophon: blog/post.md: Image is 31KB, over the 20KB maxBytes cap. Quality was
stepped down to 30, which is as far as it goes before the picture stops being
worth having. A smaller output size would do what quality no longer can.

That is a warning rather than an error because the image is still the right image. A build that renders nothing is a worse answer than one that renders something too big and says which post it was.

PNG is lossless and has no quality to trade, so under format: "png" a cap only ever reports. compressionLevel is the PNG equivalent, and it is already at its strongest by default. The setting that will actually bring a PNG down is quantise, which reduces it to a palette, though it is a decision to take once for a site rather than a step an encoder can take on its own when an image comes out over the cap.

export default defineConfig({
emitSvg: true,
});

Each image gets its source document beside it, under the same name with an .svg extension: my-post-og.png and my-post-og.svg. Under a hashed placement the hash is kept, so my-post-og.ecd0aab2.svg sits next to its own image.

That is the document to hand to a vector editor, to diff when a template changes, or to serve to anything that would rather have vectors. It is not in the manifest and it carries no rebuild stamp, since the image is what a build tracks and the document follows it.

format, quality, maxBytes and emitSvg are each part of every image’s rebuild stamp, so changing one re-renders the tree once. emitSvg is in for a version of the same reason: turning it on has to write the documents for images that are already on disk, and without it they would appear only as each post next changed.

Like fonts and the rasteriser, these are shared build inputs rather than something an individual output size can override. They are about how an image is encoded rather than what it shows. See Per-size config.