# Formats, transparency and why not AVIF

> What each format is chosen for, how alpha survives, and the benchmark that reversed itself.

## Why `format=auto` is WebP — and why that is *not* a size argument

This one has a history worth reading, because the first answer was wrong.

**May 2026.** A benchmark over 100 production samples concluded AVIF was
**4.5–9.8% larger** than WebP at equivalent quality. Case closed: keep WebP.

**June 2026.** Re-run, and the earlier result turned out to be a **methodology
artifact**, not a property of the format. The original bench had:

- encoded AVIF **from an already-lossy `lg.webp`**, not from the original, and
- compared **unmatched quality** — AVIF at q70/effort4 against WebP at
  q80/effort6, where its own SSIM column showed the AVIF was *higher* quality.

Both biases pointed the same way. Re-run from **originals** at **matched SSIM**,
AVIF is **14–20% smaller** at every delivery size, and **25–43% smaller** on
flat interiors.

So AVIF wins on size, clearly. WebP is still the default, for two reasons that
have nothing to do with bytes:

1. **Decode cost on low-end mobile.** The device mix in Central America is not
   the device mix in a benchmark. AVIF decode is what a cheap phone feels.
2. **3–6× encode time**, on a *synchronous, timeout-bounded* request path. The
   first request for a new derivative pays that.

And the product context that makes the trade obvious: **delivered file size is
not the priority driver here** — these clients have bandwidth. AVIF's real 14–20%
win buys something nobody is asking for, while its costs land squarely on
mobile thermals and time-to-first-byte, which people do notice.

**Decision: `format=auto` → WebP. AVIF only if you ask for it explicitly.**

Worth revisiting if egress cost or a bandwidth-constrained market becomes a
priority — at which point `<picture>` with AVIF on the large rungs is the path,
not flipping the default.

:::tip[The transferable lesson]
A benchmark that encodes from an already-lossy source, or compares unmatched
quality, will confidently tell you the wrong thing. Both are easy to do by
accident, and this one survived two weeks as settled fact.
:::

## Transparency: what survives and what does not

JPEG has no alpha channel. That single fact drives all of this.

| you upload | you ask for | you get | alpha |
|---|---|---|---|
| PNG with alpha | `image/png` | PNG | **kept** |
| PNG with alpha | `image/webp` | WebP | **kept** (WebP has alpha) |
| PNG with alpha | `image/webp` **on WebKit** | JPEG fallback | **flattened onto white** |
| PNG with alpha | `image/jpeg` | JPEG | flattened onto white |

**Asking for PNG is a promise the compressor keeps.** A request for
`image/png` returns PNG and nothing else — no JPEG fallback, ever. That request
means *lossless or alpha, on purpose* — icon extraction, sprite work, a cut-out
— and silently handing back a flattened JPEG would be the same class of bug as
the format mislabelling.

```ts
// A cut-out that must keep its transparency:
await nt.upload(file, {
  presets: ["original", "thumb"],
  compress: { mimeType: "image/png" },   // no fallback path exists
});
```

### Why flattening is onto *white*

Without an explicit composite, transparent pixels encode as **black** — a
cut-out product photo becomes a photo on a black background. The compressor
composites onto white before any JPEG encode, so the failure mode is "lost
transparency" rather than "ruined image".

## Keeping a PNG intact

Two different goals, two different answers:

**"I want the exact bytes back later."** → `presets: ["original", …]`. The
`original` variant is the uploaded bytes, untouched, whatever they were.

**"I do not want it re-encoded on the way up."** → that is the compressor's
`convertSize` threshold. The SDK's defaults convert large PNGs to JPEG, because
the alternative is what production actually looked like:

```
tenant twolink        20 files · image/png · avg  6.85 MB
tenant realtyone-cr  168 files · image/png · avg 13.16 MB   (2 210 MB total)
```

Those are photographs saved as PNG. For a *photo*, PNG is the wrong container
and converting is the right call. For a *graphic with alpha*, it is not — which
is why asking for `image/png` explicitly disables the whole conversion path.

## HEIC

What iPhones actually produce. Decoded **natively** where the engine can — about
**136 ms** — and only falling back to a 341 kB WASM decoder where it cannot,
which takes ~1,700 ms. Most users never fetch the decoder.

## What your visitors receive, regardless

Delivery is **always** WebP by default, no matter what the client managed to
encode. The server re-encodes every variant. The client format only
affects upload size and the archived `original` — see
[Compression in the browser](/guides/compression/).